Versions 4.45.0
Tout ce qui a été créé, modifié, supprimé ou corrigé. Le bilan par branche d’abord, le détail de chaque version ensuite. Les erreurs et les points restés ouverts y figurent aussi : un journal qui ne mentionnerait que les réussites ne servirait à rien.
Projets à venir — ce qui est décidé et pas encore commencé, par priorité
Ce document dit ce qui est PRÉVU, par ordre de priorité. Il complète les deux autres :
CHANTIERS.mdporte ce qui est ouvert et connu comme défaut ;VERSION.mdraconte ce qui est fait. Ici : ce qu'on a décidé de faire, et qu'on n'a pas commencé.⚠ Un projet qui démarre sort d'ici et devient un point de
CHANTIERS.md. Un projet abandonné sort aussi, avec sa raison — les listes qui ne font que grandir cessent d'être lues.
Comment lire les priorités
| Priorité | Ce que cela veut dire | |
|---|---|---|
| 🔴 | Haute | Quelqu'un en a besoin aujourd'hui, ou une décision attend |
| 🟠 | Moyenne | Utile et décidé, mais rien ne bloque en attendant |
| 🟢 | Basse | Souhaitable ; se fera quand le reste sera calme |
| ⏸️ | En attente | Décidé, mais suspendu à un élément extérieur |
| ✅ | Fait | Constaté fermé, avec la date et la version qui le porte |
⚠ Un projet ne passe en ✅ qu'une fois CONSTATÉ fermé, jamais dès qu'il est corrigé — et il reste ici quelque temps plutôt que de disparaître : ce qui l'a rendu nécessaire vaut souvent mieux que ce qui l'a réglé.
🔴 Traduire le site — plusieurs langues, au choix du visiteur
Demandé le 02/09/2026. Détecter la langue du visiteur, et lui laisser en changer par un menu.
Ce que la mesure dit : 26 367 mots de texte visible, sur 50 fichiers — 15 757 pour le site public, 10 528 pour le panel. Soit quatorze jours de traduction humaine par langue, ou environ 3 € de traduction automatique — mais surtout, tout est à refaire à chaque modification d'une page.
Trois décisions à prendre avant d'écrire une ligne :
- ⚠⚠ Le pays n'est pas la langue. Détecter par l'adresse IP demande une base GeoIP à tenir à jour et se trompe souvent — Belgique, Suisse, Canada, ou simplement quelqu'un en déplacement. L'en-tête
Accept-Language, que le navigateur envoie déjà, dit la langue que la personne a choisie. Le menu déroulant reste, pour ceux qui veulent autre chose. - ⚠⚠ La charte (1 882 mots) et le règlement (1 492 mots) ne seront PAS traduits automatiquement. C'est ce que les gens acceptent en se connectant : deux versions qui ne disent pas exactement la même chose posent la question de savoir laquelle fait foi.
- ⚠ Le panel s'adresse à l'exploitant, pas aux visiteurs. Ses 10 528 mots doublent le travail pour une seule personne.
Le découpage proposé, du plus utile au moins :
| Lot | Mots | Pourquoi |
|---|---|---|
| Le parcours visiteur — accueil, chat, inscription, contact, télécharger | ~5 000 | c'est là que quelqu'un décide de rester ou de partir |
| Le reste du site public — guide, installation | ~7 000 | utile, pas bloquant |
| Le panel | ~10 500 | pour l'exploitant seul |
📌 Le vrai chantier n'est pas la traduction, c'est l'extraction. Les phrases sont aujourd'hui écrites dans le HTML des pages ; il faut les remplacer par des appels à une fonction de traduction. Mécanique, mais 50 fichiers.
Prochain pas : une maquette sur l'accueil seul, pour voir ce que ça donne avant de s'engager sur les 49 autres pages.
🔴 Éprouver le site sur tous les navigateurs
Demandé le 02/09/2026. Le site est du HTML et du CSS standard, donc a priori portable — mais « a priori » ne vaut rien tant que ce n'est pas mesuré.
Ce qu'il faut regarder, par ordre de risque :
- Safari (macOS et iOS) — le plus susceptible de diverger. Le WebSocket du chat, et les propriétés CSS récentes comme
color-mix(), employée dans la feuille du client. - Firefox — moteur différent de Chrome ; les écarts y sont rares mais réels.
- Chrome et Edge — même moteur, déjà éprouvés de fait.
⚠ Le chat est le point sensible : il ouvre une connexion permanente, et c'est là que les navigateurs se distinguent le plus.
⏸️ macOS — en attente d'un moyen de tester
Ouvert le 31/08/2026. La configuration est écrite et prête dans electron-builder.yml, avec les deux architectures et les raisons de chaque choix. Il ne manque que la machine.
Trois obstacles, et le troisième est le vrai :
electron-builderne peut pas produire de.dmgdepuis Windows —hdiutiln'existe que sur macOS.- Une machine virtuelle sur PC ne construit et n'éprouve que l'architecture
x64: tous les Mac vendus depuis 2020 sont enarm64. - ⚠⚠ Sans signature Apple, le paquet s'annonce « endommagé » au premier lancement — pas « non signé ». Quelqu'un qui ne connaît pas macOS en conclut que le logiciel est corrompu, referme, et n'écrit jamais pour le dire. La mise à jour automatique ne fonctionne pas non plus.
📌 Le blocage n'est pas technique mais une décision : un compte développeur Apple, à 99 $/an. Sans lui, mieux vaut annoncer « macOS : à venir » qu'un paquet qui fait peur.
Les chemins possibles, si la décision est prise : GitHub Actions (matériel Apple, légal, et il signe si on lui fournit le certificat), un Mac d'occasion, ou un service de location.
✅ Le site installable sur téléphone — fait le 03/09/2026
Demandé après avoir écarté une application native : le panel est déjà responsive, une application de magasin aurait coûté 99 $/an et un Mac, aurait été rejetée par Apple comme simple habillage d'un site, et aurait mis chaque correctif derrière 24 à 48 h de revue.
Constaté sur Android : icône sur l'écran d'accueil, ouverture en application, réaction au mode avion. Deux manques sont apparus à l'usage — les tailles d'icônes, et un bouton pour fermer, qui n'existait pas en mode plein écran.
⚠ Ce qui reste ouvert : le panel n'a toujours pas été mesuré sur un écran de téléphone, et les voies (b), (c) et (d) de MOBILE.md restent non engagées.
✅ La mise à jour sous Linux — corrigée le 03/09/2026, publiée en 0.6.10
Ouvert et fermé le même jour. Le client choisit désormais son paquet dans la clé plateformes : l'installateur sous Windows, le .deb confié au gestionnaire de paquets du bureau, l'AppImage déposée avec son chemin. Le format se reconnaît à la variable APPIMAGE qu'exporte le runtime AppImage.
⚠ Ce qui reste vrai, et qui ne se répare pas : les postes Linux déjà installés en 0.6.9 ne recevront pas ce correctif tout seuls — pour le recevoir il faudrait réussir une mise à jour, c'est-à-dire précisément ce qui ne marchait pas. Les notes de la 0.6.10 le disent, et renvoient à la page Télécharger pour le passage manuel, une seule fois.
📌 La leçon, elle, ne se referme pas : deux listes des mêmes choses à deux endroits, dont une seule est dérivée. La clé plateformes existait depuis le 31/08 et personne ne la lisait ; tous les constats étaient verts des deux côtés, parce qu'ils mesuraient ce qui est annoncé, jamais ce qui aurait dû l'être.
🟠 Windows ARM64
Une ligne de configuration, coût quasi nul. Peu de demande aujourd'hui — à faire en même temps qu'une autre passe sur les paquets.
🟠 Un paquet RPM pour Fedora
L'AppImage couvre déjà Fedora et toute autre distribution. Un .rpm apporterait l'entrée de menu et la désinstallation propre, comme le .deb pour Debian.
⚠ Non fait à dessein : personne dans ce projet n'a de Fedora pour le constater, et un paquet qu'on ne peut pas essayer est un paquet dont on ne sait rien.
🟢 Le nom de la distribution dans le client
Le client affiche « Linux » sans dire laquelle. process.platform ne connaît que « linux » ; la distribution se lit dans /etc/os-release, côté processus principal.
⚠ Écrire « Debian » sur une Fedora serait faux une fois sur deux — et c'est la première chose que lit quelqu'un qui signale un défaut. D'où « Linux » tout court en attendant.
🟢 La partyline dans OverScript
Demandé le 02/09/2026, et suspendu à une question de doctrine plutôt qu'à du code : la partyline donne l'interpréteur Tcl du bot, en telnet clair. La doctrine du projet — citée dans irix-botnet.tcl — veut que ces mots de passe ne soient jamais posés automatiquement.
📌 La question a été posée à l'exploitant ; elle attend une réponse.
🔴 haute · 🟠 moyenne · 🟢 basse · ⏸️ en attente d’un élément extérieur. ⚠ Un projet qui démarre sort de cette liste et devient un chantier ouvert ci-dessous — les listes qui ne font que grandir cessent d’être lues.
Ce qui reste ouvert — défauts en cours, travaux en attente, et les endroits qui résistent
📌 Ce qui est PRÉVU et pas encore commencé vit dans
AVENIR.md, par ordre de priorité — traduction du site, compatibilité des navigateurs, macOS. Ici : ce qui est ouvert et connu comme défaut. Un projet qui démarre passe de l'un à l'autre.
Ce document dit ce qui N'EST PAS fait.
VERSION.mdraconte ce qui l'est ;ROADMAP.mdporte le plan de route par phases. Ici : les défauts encore ouverts, les travaux en attente, et les endroits où le projet résiste — avec, pour chacun, ce qu'il faudrait pour le fermer.⚠ Un point n'est retiré d'ici qu'après avoir été CONSTATÉ fermé, jamais après avoir été corrigé. Cinq points de ce carnet se sont révélés périmés le 07/08/2026 : le défaut annoncé n'existait plus, ou pas comme il était décrit. Une décision d'exploitation attendait sur l'un d'eux depuis trois semaines, pour rien.
⚠⚠ NE JAMAIS LIRE CE PRÉAMBULE POUR CONNAÎTRE L'ÉTAT. Il a porté pendant des semaines un journal de revues successives, et il était entièrement périmé au 16/08/2026 : il annonçait le point 18 ouvert alors que ce même fichier le déclare clos depuis le 15/08 à 19:06:20, une section rouge « vide » puis « ne portant qu'un seul point » alors qu'elle en portait plusieurs, et que le point 9 ne devait pas s'ouvrir alors qu'il l'est sur décision de SerVuS. Le récit des revues a donc été retiré : c'est exactement le genre d'affirmation d'état en français qu'aucun banc n'attrape, et le seul remède est de dériver la liste des titres
###.
⚠ Comment lire ce document — l'état se DÉRIVE, il ne se lit pas
Le tri au 23/08/2026 (soir), dérivé des titres ### — à refaire, jamais à recopier :
⚠⚠ CE TABLEAU PORTAIT DES COMPTES, ET ILS ONT MENTI — RETIRÉS LE 24/08/2026. Il annonçait « 6 🟡 » en y comptant le 42 et le 26, tous deux fermés la veille ; il décrivait le 43 comme « il manque la pose sur un /etc écrivable » quand son propre corps dit qu'il ne peut se fermer ni ici, ni ailleurs ; et il affirmait « 🔴 rien n'est corrigé — 0 » alors que le 45 est ouvert et que rien n'en est porté au dépôt. Les 45, 46 et 47, ouverts depuis, n'y figuraient pas du tout.
C'est le travers que ce document reproche ailleurs à ROADMAP.md et à PRET-ROBOT.md, qui ont retiré les leurs pour la même raison : un chiffre écrit à la main ne survit pas à la séance qui l'écrit.
L'état se DÉRIVE, il ne se lit pas — les titres portent le marqueur, et eux seuls font foi :
grep -n '^### [0-9]' Markdown/CHANTIERS.md | grep -v '✅' # ce qui n'est pas fermé
grep -c '^### [0-9]' Markdown/CHANTIERS.md # le total
⚠ Et un marqueur de titre ment aussi : le 26 a porté 🟡 pendant une journée alors que son corps, deux lignes plus bas, le déclarait fermé. Ouvrir le point et lire son encadré « Pour fermer » reste le seul contrôle sûr.
⚠⚠ Le 21/08, les points 42 et 43 sont passés de 🔴 à 🟡 — mais le 42 a d'abord été RETOURNÉ par la mesure, dans les deux sens : il sur-disait sur la gravité (ces flags gardent la partyline, pas les commandes du bot, et le mot de passe reste exigé) et sous-disait sur la portée (c'est le code qui fabriquait le masque ouvert, et il le refait chez chaque tiers). Le détail est en tête du point. Le registre donne une piste, jamais un diagnostic — une fois de plus.
⚠ Le 20/08 a fermé trois points (39, 40, 41) et en a ouvert trois (41→43, 42, 43). Ce n'est pas un échec : aucun des trois nouveaux n'a été trouvé par une relecture. Le 42 parce qu'un utilisateur s'est fait expulser, le 41 et le 43 en installant une unité à la main. Le registre ne grossit pas parce qu'on écrit mal ; il grossit parce qu'on fait marcher les choses.
⚠ Le 20/08, le point 40 est passé de « rien n'est corrigé » à « constaté fermé » en une séance — et ce n'est pas le correctif qui l'a fait avancer, c'est l'essai. Installer la veille était un geste ; l'éprouver en coupant le service deux fois au lieu d'une a trouvé un défaut que trois bancs verts ne voyaient pas. Le second essai est celui qui trouve ce qui ne se réarme pas.
⚠ Et deux points ont été ouverts dans le même mouvement, 41 et 42 — tous deux trouvés non par une relecture, mais en faisant marcher les choses : l'un en installant une unité à la main, l'autre parce qu'un utilisateur s'est fait expulser. Le registre ne grossit pas parce qu'on écrit mal ; il grossit parce qu'on regarde.
⚠ Le 🟡 n'est pas un demi-✅, c'est un piège nommé. Les quatre défauts du 16/08 étaient tous dans cet état sans le savoir : corrigés ici, absents de ce qui est distribué, et un banc vert par-dessus. Un point n'y reste que le temps de prendre le constat qui manque.
⚠⚠ CE DOCUMENT DIT L'INVERSE DE CE QU'IL ANNONCE. La très grande majorité de ses lignes décrit ce qui EST fait, et non ce qui reste. Les deux plus gros blocs du fichier sont l'un et l'autre clos. ⚠ Aucun compte de lignes n'est écrit ici : celui qui y figurait annonçait 4 092 lignes pour un fichier qui en portait 4 678, et il se périmait à chaque séance — wc -l le donne exactement. Extraire les points fermés réduirait le document des deux tiers. C'est une décision de SerVuS, et elle est en attente — ⚠ trois choses casseraient mécaniquement (Web/site/versions.php, DISTRIB_RACINE_SITE, la liste en dur de test-markdown.php), et ~25 fichiers renvoient à des numéros de point qui se périmeraient sans un seul banc rouge.
⚠ Rangement contre convention : les points 22, 23 (clos) sont restés sous « Défauts ouverts », et 27, 29 (clos) sous « Travaux à effectuer », alors que 18, 19, 20 et 21 ont bien été déplacés après constat. Le document applique sa propre règle de façon inégale — se fier au marqueur du titre, pas à la section qui le contient.
Le 15 — la veille de Windows gèle WSL, trois scans horaires perdus sans un mot — a été tranché par l'option (c) : l'assumer, SerVuS ayant délégué l'arbitrage. Le gel n'est pas corrigé et ne le sera pas ; il est borné, et c'est ce qui rend la décision tenable : il coûte un retard, jamais une donnée. ⚠ La discipline qui rend ce coût acceptable ne repose plus sur la vigilance — chaque
bind timedéclare son rattrapage sur une ligne# RATTRAPAGE:, ettest-bindtime.tclrougit sur un bind ajouté sans elle. Deux confirmations mensongères ont été attrapées en l'écrivant, dont un banc qui gardait la porte en regardant la serrure.⚠ Deux fausses pistes ont été suivies avant d'y arriver — le journal accusé d'être mort alors qu'il acceptait l'écriture, puis une fuite de descripteurs supposée sur 8 fichiers ouverts. ⚠ Le point 1 a été trouvé en contradiction avec lui-même : classé « défaut ouvert », sa ligne « Pour fermer » déclarait qu'il est clos côté code. Il n'a PAS été déplacé sur cette seule lecture — un point ne sort d'ici que CONSTATÉ fermé. Il l'a été ensuite : mesuré en production le 14/08, cinq jours après sa fermeture, les trois gardes d'
amorce_oken place, son banc vert à 15 vérifications,antimot=ouienwarnsur quatre salons et les 17 mots du filtre présents. Alors seulement il a rejoint les travaux, et ses trois avertissements ont été portés dans « Les endroits où ça résiste » — ils valent indépendamment de lui. La section 🔴 ne porte donc plus que de vrais défauts actifs.⚠⚠ LE COMPTE QUI SUIVAIT ICI — « elle n'en porte plus qu'un seul, le 15 » — A ÉTÉ RETIRÉ LE 25/08/2026, PÉRIMÉ. Le 15 a été tranché depuis (option (c), l'assumer, voir plus bas), et la section rouge porte autre chose. C'est très exactement le travers que ce préambule dénonce quatre paragraphes plus haut, et il l'avait recommis. Ce que porte la section 🔴 se DÉRIVE — les titres
###entre## 🔴 Défauts ouvertset la section suivante, filtrés sur l'absence de ✅ — et ne se lit jamais ici.Revue du 13 août 2026 — points 2 et 11 mesurés avant d'y travailler, et les deux mentaient. Le 2 se contredisait dans sa propre page : son titre réclamait une bascule de charge que sa ligne « Pour fermer » déclare écrite et constatée depuis la veille. Le 11 présentait trois pistes comme si aucune n'avait été prise, alors que la troisième tournait en service depuis le 12/08. ⚠ Ni l'un ni l'autre n'était une péremption par le temps — les deux étaient faux le jour où ils ont été écrits.
Revue précédente : 12 août 2026, séance entière — points 2, 3, 5, 6, 7 et 8 revus et trois fermés par constat (3, 6, 8). La précédente datait du 11 août ; celle du 9 août (4.16.0) avait eu une seconde passe le même jour où un sixième point périmé est tombé : l'historique de modération, qui existait déjà et affichait déjà.
Seconde passe du 12/08, en fin de séance : le point 12 est né — la console d'IriX, écrite le 07/08 et jamais employée jusque-là, faute d'un masque au userfile ; elle est close, les trois commandes constatées en service. Le point 2 a été retourné : le botnet, écarté le 09/08, est repris — ses trois objections sont gardées, deux restent vraies. Et le point 13 est ouvert : l'IPv6 est un chemin d'entrée réel que trois documents déclaraient inexistant, périmés sans bruit par le passage de WSL en mode miroir.
⚠ Le journal accusait un innocent depuis le 27/07. Les trois erreurs SQL de chaque démarrage —
Access denied for user 'servus'— ne venaient ni de la base ni des droits MySQL, mais d'un appel placé 12 000 lignes trop tôt dansirix-register.tcl, et qui faisait DOUBLON avec l'appel légitime. 171 occurrences, aucune conséquence, et une fausse piste à chaque lecture du journal. Fermé et constaté le 12/08 — voir « Un appel au niveau du fichier s'exécute AVANT la configuration ».⚠ Le 12/08, ce carnet a été pris en flagrant délit d'affirmation fausse — et pas périmée par le temps, cette fois : fausse le jour où elle a été écrite. Le point 2 disait des deux compteurs de charge « elles sont d'accord aujourd'hui ». Elles ne l'étaient pas : le banc de croisement que ce même paragraphe réclamait les a trouvées en désaccord sur la casse dès sa première passe. Une affirmation d'état écrite sans être mesurée est fausse par défaut, même sous la plume de celui qui réclame la mesure.
⚠ La revue du 11/08 a trouvé trois écarts, et aucun n'était une régression — tous les trois disaient un état vrai le jour où il avait été écrit, et faux le lendemain :
autolimitremis ànon(il a été coché à 19h25 le soir même, par le panel),chat_histà 7 lignes (il y en a 30), et trois cases deROADMAP.mdannonçant des manques comblés depuis. Ce carnet ne se périme pas par erreur, il se périme par le temps. La seule parade est de mesurer le point sur lequel on travaille, au moment où l'on y travaille.📌 Le point 9 n'est pas de la même nature que les autres. Ce n'est pas un défaut ouvert mais un chantier — le découpage du projet en quatre éditions, demandé le 11/08/2026 « pour quand tout sera stable ». Il vit dans
Markdown/EDITIONS.md.⚠⚠ LA SUITE DE CE PARAGRAPHE — « et il ne doit pas s'ouvrir tant que les huit premiers sont là » — A ÉTÉ RETIRÉE LE 25/08/2026, PÉRIMÉE. Le point 9 est OUVERT depuis le 14/08/2026, sur décision de SerVuS : ses phases 1 et 2 sont faites. Ce préambule le disait déjà dans son avertissement d'ouverture, tout en affirmant le contraire ici — il se contredisait d'un bout à l'autre de lui-même. ⚠ Et le point 48 dépend du 9, ce qui rendait la phrase retirée doublement trompeuse. L'état du 9 se lit dans son propre point, jamais ici.
🔴 Défauts ouverts
74. ✅ HUIT FORMES DE LIGNE DE RÉSUMÉ — tranché le 04/09/2026 : l'outil apprend, les bancs ne bougent pas
Ce que c'est. Chaque banc Tcl annonce son résultat à sa façon. Relevé en voulant mesurer trente bancs d'un coup :
TOUT PASSE — 24 vérifications.
TOUT PASSE — 88 vérifications, dont 14 mutations attrapées.
15 réussites, 0 échecs
1 ÉCHEC(S) sur 14 vérifications.
── 23 constat(s) tenu(s), 0 échec(s) ──
── 14 constat(s), 0 échec(s) ──
── 44 constats, 0 échec(s) ──
constats : 32 ok, 0 en échec
TOUT VERT — 32 constats
🔴 CE POINT ANNONÇAIT SEPT FORMES. IL Y EN A HUIT — la dernière, TOUT VERT, est celle de test-webexpire.tcl, et elle a été ratée au comptage du matin même, deux heures plus tôt. 📌 Compter ne suffit pas : le compte lui-même est une mesure, et il se rate. Le releveur qui l'a trouvée ne l'a pas trouvée en lisant — il l'a trouvée en rendant ? sur un banc, parce qu'il distingue « zéro » de « pas lu ».
⚠ Ce n'est pas une question de goût. Tools/bancs.sh s'en tire — il lit le code de sortie, pas la ligne. Mais tout outil qui veut lire un résultat doit connaître les sept formes, et celui qui n'en connaît que deux rend « aucune ligne de résumé » pour onze bancs qui passent très bien. 📌 Une mesure qui ne reconnaît pas ce qu'elle voit rend « rien », et rien ressemble à tout va bien — le motif du jour, rencontré ici pour la quatrième fois.
⚠⚠ Le piège est pire qu'il n'en a l'air. Un banc dont le format change — parce qu'on l'a réécrit — sort silencieusement du champ de tout outil qui l'échantillonnait. Rien ne le signale : l'outil dit simplement une chose de moins.
✅ TRANCHÉ le 04/09/2026 — c'est l'OUTIL qui apprend, pas les 57 bancs qui changent
🔴 On n'uniformise pas. Tools/bancs.sh lit le code de sortie, pas la ligne : il n'a jamais eu de problème avec ces huit formes. Le besoin d'une forme unique était celui de l'outil qui lit — donc c'est l'outil qui apprend. Réécrire cinquante-sept bancs qui marchent pour satisfaire un lecteur, c'est prendre le risque du côté où il n'y en avait pas.
Tools/bilan-constats.sh connaît les huit formes et répond à une question que le projet n'avait jamais posée : combien de constats le gardent ? — un chiffre que « 149 bancs » cache, puisqu'un banc de 4 constats et un banc de 196 y pèsent pareil.
⚠⚠ Il distingue « zéro » de « pas lu ». Un banc dont le résumé lui échappe n'est pas compté comme vide : il est nommé, et le total est alors annoncé comme un plancher, pas comme un compte. C'est exactement ce qui a fait trouver la huitième forme.
⚠ Le plancher du plancher : sous vingt bancs lus, l'outil s'arrête — un chemin qui a bougé rendrait sinon « 0 constat » avec un code de sortie 0, et ce silence passerait pour une bonne nouvelle.
📌 Un banc NEUF emploie la forme la plus explicite — ── N constat(s), M échec(s) ── — parce qu'elle porte les deux nombres dans tous les cas. La discipline vaut pour ce qu'on écrit, pas pour ce qui existe.
73. ✅ RIEN NE VÉRIFIAIT QUE LES SCRIPTS D'IriX SE CHARGENT — 04/09/2026
Ce que c'est. Cent quarante-huit bancs éprouvaient le contenu des scripts d'IriX. Pas un seul ne vérifiait qu'ils peuvent être lus par Tcl de bout en bout.
⚠⚠ Et ce n'est pas un manque théorique. Le 26/08/2026, une procédure inexistante appelée au chargement a coupé le serveur à la 89ᵉ seconde. Rien ne l'avait signalé : Tcl n'a pas de bash -n, info complete ne voit que l'équilibrage des accolades, et tous les bancs extraient des procédures une par une — ce qui n'exécute jamais le corps du script. C'est de là que vient le contrôle manuel « à ~90 s après redémarrage », qui n'est pas un banc mais une habitude.
Ce qui l'a fait écrire aujourd'hui : deux modifications du code d'IriX — les binds du point 66, le retrait des quatre procédures du point 56 — qui ne seraient éprouvées qu'au redémarrage, et un redémarrage raté coupe le bot. Écrire un correctif qu'on ne peut pas éprouver avant de le mettre en service demande d'abord un moyen de l'éprouver.
Ce que fait IriX/tests/test-chargement.tcl : il bouchonne la soixantaine de commandes qu'Eggdrop fournit et que tclsh n'a pas — bind, putserv, chanlist… — puis il source le point d'entrée, qui entraîne tous les autres. 231 binds sont posés, et deux constats les lisent :
- ⚠⚠ chaque bind pointe-t-il sur une procédure qui existe ? Eggdrop accepte un
bindvers une procédure absente sans un mot : il ne s'en plaint qu'au moment où l'événement arrive, donc en service, devant des gens ; - les binds ajoutés le jour même sont-ils vraiment posés, et les quatre verbes retirés ne reviennent-ils pas ? Les voir dans le fichier ne prouve pas qu'ils survivent au
source.
⚠ Ce que ce banc ne fait PAS. Ce n'est pas un vrai démarrage : les bouchons rendent des valeurs vides là où Eggdrop rendrait des données, donc une branche qui dépend d'un serveur réel ne s'exécute pas. Il attrape ce qui casse à la lecture — syntaxe fautive, procédure appelée avant d'exister, source vers un fichier absent. C'est précisément la famille qui coupe un démarrage, et c'est tout ce qu'il promet.
Mutation : 6 blessures sur 6 vues, dont le défaut du 26/08 lui-même — une procédure inexistante appelée au chargement — qui fait tomber 7 constats sur 12.
72. ✅ HUIT ANCRES DE MUTATION AVAIENT DÉRIVÉ, ET NEUF FRAPPAIENT À CÔTÉ — FERMÉ le 04/09/2026
Ce que c'est. bancs/mutation.js (OverScript) blesse le code pour vérifier qu'un banc rougit. Huit de ses ancres ne s'appliquaient plus, et quatre autres s'appliquaient à la mauvaise occurrence. Douze gardes n'étaient donc éprouvées par rien.
⚠ La cause n'est pas celle qu'on croit. Ce n'était pas une refonte de code : c'est le commit des accents (51ca514, 03/09 13:31) qui a réécrit téléchargement, refusée, délai dans les sources et dans neuf bancs — mais pas dans mutation.js, écrit 2 h 21 plus tôt. Huit accents, aucune structure touchée.
🔴🔴 ET LES QUATRE AUTRES SONT PIRES QUE LES HUIT. Une ancre dérivée est nommée par blessures-restantes.js. Une ancre présente plusieurs fois ne l'est pas : String.replace frappe la première occurrence, la mutation s'applique, un banc rougit, et tout paraît en ordre — pendant que la garde annoncée n'est mesurée par personne. Deux mutations étaient un doublon octet pour octet, et la GARDE 2 de l'identification automatique n'était éprouvée par rien.
Ce qui ferme le point : les huit ancres rendues à leur orthographe, les quatre rallongées pour viser leur cible, et deux mutations ajoutées pour des gardes qui n'en avaient aucune (la vérification HTTPS du journal des versions, et le durcissement de la fenêtre de démarrage). 263 blessures, aucune dérivée.
✅ Le contrôle d'unicité est posé — 04/09/2026, l'après-midi même
⚠⚠ Il existait, et il ne se posait JAMAIS la question. Le test « cette ancre est-elle unique ? » vivait sous le continue de la branche « la blessure n'est pas en place ». Sur un source intact — le cas de toutes les campagnes — l'outil retournait avant de l'atteindre, et annonçait « Source intact : 263 blessure(s) ». Une garde écrite, jamais appelée : le motif du jour, pour la troisième fois.
📌 L'ambiguïté d'une ancre n'est pas un fait sur l'état du fichier, c'est un défaut de la blessure. Elle se pose donc avant tout le reste, et sans regarder si le source est blessé.
Cinq ancres étaient ambiguës, et l'une d'elles frappait franchement à côté : « la déconnexion oublie les fenêtres privées » visait if (t.type === 'serveur') continue;, qui existe deux fois dans app.js — et replace() touchait celle d'une boucle d'affichage, pas celle de la déconnexion. La blessure abîmait du code juste, laissait intacte la garde qu'elle prétendait éprouver, et était comptée comme réussie.
⚠ Trois listes, pas une. Le premier jet mettait 21 blessures dans le même sac sous le titre « elles ne mesurent pas ce qu'elles annoncent ». C'était faux pour seize d'entre elles : leur vers contient leur de, donc elles se posent très bien — c'est la détection après coup qui est aveugle, pas la mesure. Un outil qui accuse juste et faux ensemble s'apprend à être ignoré en entier.
Deux ajouts d'outillage, tombés du même travail :
- un filtre —
node bancs/mutation.js "<motif>"— parce qu'éprouver cinq ancres réécrites demandait d'en rejouer 263, soit des heures. Le remède pris jusque-là était un script jetable dans le scratchpad, qui meurt avec la séance en emportant sa règle ; - le plancher que ce filtre a révélé : un motif ne correspondant à rien donnait « 0 blessures, 0 trou(s) » puis « Le banc sent chacune des blessures », et une sortie 0. Zéro mesure, et l'outil annonçait la victoire. ⚠ Le cas n'arrive pas qu'avec un filtre mal tapé : un
CIBLESvide ou un chemin qui a bougé donnent le même silence.
71. ✅ DEUX CLÉS QUE RIEN NE LIT DANS LE MODÈLE D'IDENTITÉ — 04/09/2026
Config/identity.json.example déclarait netadmin_vhost et admin_vhost. Tools/identite-config.sh lit irc.oper_vhost et irc.user_vhost. Un exploitant tiers aurait rempli deux champs sans aucun effet, et ses vhosts n'auraient jamais été posés.
🔴🔴 ET LE SILENCE EST TOTAL, C'EST LÀ TOUT LE DÉFAUT. identite-config.sh teste [ -n "$OPER_VHOST" ] avant d'agir : une clé absente ne lève rien, ne dit rien. Le contrôle passe — simplement il ne contrôle plus rien. C'est la forme la plus coûteuse du défaut de famille de ce projet : juste ici, muet ailleurs.
⚠ Ce fichier est le seul fichier d'identité qu'un tiers voit. Il le copie, le remplit, et son réseau en dérive son nom, son domaine, ses adresses et ses vhosts.
📌 Même famille que le point 70, qui se ferme d'ailleurs en s'appuyant sur ce modèle — et l'on a découvert le lendemain que deux de ses clés ne servaient à personne.
Ce qui ferme le point : Web/tests/test-identite-modele.php, 14 constats. Il compare les deux fichiers structurellement — aucune liste écrite à la main, elle est dérivée d'identity.json — et croise avec le code : chaque clé que lire cherche dans l'outil doit exister dans le modèle. Sans ce second contrôle, les deux fichiers pourraient concorder parfaitement… sur des clés que plus personne ne lit. Mutation : 5 blessures, 5 issues attendues, la première étant le défaut réel remis en place.
⚠ Ce que le banc ne garde pas : que identite-config.sh DISE quelque chose quand une clé manque. Il continue de sauter en silence — voir le point 72 pour la même famille de silence.
70. ✅ UNE CONSTANTE POSÉE DANS LE SEUL FICHIER QUI NE VOYAGE PAS — 03/09/2026
Le lien Facebook demandé par SerVuS lisait RESEAU_FACEBOOK, que j'avais déclarée dans Web/inc/config.php. Le dépôt exclut ce fichier — il porte les mots de passe. La constante existait donc sur le poste de développement et nulle part ailleurs : ?p=contact a rendu « Uncaught Error: Undefined constant » en production, tronquée à 10 Ko au lieu de 22.
⚠⚠ Et le banc était VERT pendant ce temps. Ses 28 constats lisaient des fichiers, et sur cette machine le fichier était bon. Lire le code ne dit rien de ce qui sort.
⚠ Une seconde faute, plus grave, dans le même correctif : après avoir déplacé la constante dans identity.php (versionné), j'y ai mis notre adresse comme valeur de repli. Toute installation tierce aurait affiché la page Facebook d'Overtchat au pied de chacune de ses pages, sans que rien ne le lui dise. Le commentaire d'à côté affirmait pourtant qu'un exploitant « y met la sienne ». La question n'est pas « peut-on la changer ? » mais « que fait-elle quand personne ne la change ? »
Ce qui ferme le point : la valeur vit dans Config/identity.json (clé social.facebook), le repli du code est la chaîne vide, le modèle identity.json.example la porte vide, et Markdown/RESEAU.md la documente. Le banc test-lien-facebook.php (45 constats) demande les pages au serveur au lieu de lire des fichiers, et refuse tout repli non vide.
69. ✅ UN KICK QUI N'A JAMAIS ÉTÉ ENREGISTRÉ — 03/09/2026
À 15:18:04, un !k servus tapé depuis un téléphone a produit event_type = « kick servus (Expulsé par SerVuS) » : 32 caractères pour un varchar(30). MySQL est en mode strict — il ne tronque pas, il refuse. L'expulsion a eu lieu sur le réseau et n'a jamais été enregistrée. C'est l'alerte « Écriture SQL refusée » que SerVuS a vue.
⚠ 35 des 57 appels à log_chanadmin composaient ce champ avec une variable — say $msg, mode $text, ban $mask, les trois verdicts de l'IA. Et dans evt_record, raw était la seule valeur bornée : la seule dont la colonne soit un TEXT, donc la seule qui n'en avait pas besoin. Six colonnes sur sept écrivaient sans garde.
Ce qui ferme le point : ::soc::sql_col taille chaque valeur à sa colonne au point d'écriture. Banc IriX/tests/test-events-longueurs.tcl (32 constats), qui croise les longueurs du CREATE TABLE et les nombres du code — changer l'un sans l'autre fait rougir. Mutation : 8 blessures, 8 issues attendues.
⚠ Ce que le banc n'attrape pas : il lit le CREATE TABLE, pas le schéma réel du serveur. Une colonne modifiée à la main en production le laisserait vert.
68. ✅ LE SITE DEVIENT INSTALLABLE — PWA, demandée et FERMÉE le 03/09/2026
Décidée après avoir écarté une application native : le panel est déjà responsive, une application de magasin aurait coûté 99 $/an et un Mac, aurait été rejetée par Apple comme simple habillage d'un site (règle 4.2), et aurait mis chaque correctif derrière 24 à 48 h de revue.
Constaté par SerVuS sur son téléphone : icône sur l'écran d'accueil, ouverture en application, réaction au mode avion. Deux manques sont apparus à l'usage et ont été corrigés dans la foulée : les tailles d'icônes déclarées explicitement, et un bouton « Quitter » — rien ne permettait de fermer une application en display: standalone.
⚠⚠ Ce que les bancs NE PEUVENT PAS mesurer, et c'est écrit en tête de test-pwa.php : ni l'enregistrement réel du service worker, ni le hors-ligne. Le navigateur d'essai de cette machine les refuse tous — un service worker vide y échoue aussi, mesuré exprès pour distinguer le code du contexte. Tout cela a été constaté à la main, par SerVuS, à travers une page de diagnostic écrite pour l'occasion puis retirée.
📌 Voir Markdown/MOBILE.md, dont l'affirmation centrale — « ce document ne décrit rien qui existe » — était devenue fausse et a été corrigée le même jour.
67. ✅ CHAQUE ANOMALIE MONTRE SA PAGE — demandé et FERMÉ le 03/09/2026
Demandé par SerVuS : « faudra générer des pages comme le 404 qui reste au format Service-Overtchat, pour toutes les pages qui tournent en erreur ». Avant, seule la 404 avait la sienne. Un refus d'accès au panel rendait une ligne noire sur fond blanc, un fichier de téléchargement absent rendait seize octets (Fichier inconnu.), et un export de base qui échouait affichait le message de l'exception PDO — avec le SQL et des chemins de fichiers.
Trois familles, parce qu'il y a trois situations :
| Qui sert | Quoi | Codes |
|---|---|---|
| le site public | Web/site/erreur.php via site_erreur() | 400, 403, 405, 429, 500 |
| le site public | Web/site/404.php via site_404() | 404 seule — elle propose où aller |
| le panel | panel_erreur() · Web/inc/layout.php | 400, 403, 404, 500 |
| Apache | Config/apache/maintenance.html | 503 — un arrêt VOULU |
| Apache | Config/apache/indisponible.html | 500, 502, 504 — un défaut SUBI |
⚠⚠ Les outils gardent leur réponse sobre. ?p=dl est atteint par un lien sur lequel on clique et par curl depuis l'installateur : le choix se fait sur l'en-tête Accept (site_accepte_html()), jamais sur une devinette. Un navigateur reçoit la page, curl ses onze octets — exactement comme avant.
⚠ Deux pages Apache, et la duplication de leur style est VOULUE : elles s'affichent quand le site est absent, donc une feuille commune serait servie par le serveur qu'elles remplacent.
🔴 Le défaut le plus intéressant est venu de la règle 5, et il ne se voyait pas : l'installateur ne posait que maintenance.html. Une installation tierce aurait eu ses ErrorDocument 500/502/504 pointant sur un fichier absent, et personne ne l'aurait su avant sa première panne. La règle porte désormais un motif *.html, et la désinstallation aussi.
⚠ Ce qui n'est PAS constaté : la 502 réelle. Il faudrait couper le site pour la voir. Ce qui est constaté : les pages existent, sont autonomes, diffèrent, et la conf les branche — 39 constats dans Web/tests/test-pages-erreur.php, 10 blessures de mutation, toutes vues.
66. ✅ La limite qui ne redescendait pas — CONSTATÉE FERMÉE le 04/09/2026 en service · 🟡 le bruit de démarrage reste
Ouvert sur une observation de SerVuS : « pas un peu bizarre tout ça ? », devant la séquence de modes et de sujets qui suit un redémarrage. Rien n'est cassé — mais trois choses méritent d'être sues, et une seule est un vrai défaut.
Ce qui n'en est pas un. Les deux « IriX » de l'affichage sont deux identités : 003AAAAAA le service ULine, 10AAAAAAC le bot Eggdrop connecté en client. Le +tn reposé ne vient d'aucun script d'IriX — tous trient les lettres (lsort) et produiraient +nt ; c'est Eggdrop lui-même qui réapplique son chanmode +tn d'IriX.chan. Et le +l est une limite dynamique, présents + 2 (IriX-sql.tcl:11906), vérifiée en direct : 4 → 5 à une arrivée, 5 → 4 au départ.
⚠ Le vrai défaut, mesuré : la limite ne suit PAS un QUIT
IriX-binds.tcl:230-242 lie join, part, nick, kick — aucun bind sign. Mesuré par contraste, dans le même essai :
| Départ | Limite |
|---|---|
PART | 5 → 4 |
QUIT | 5 → 5 |
⚠ Le sens est permissif : la limite reste trop haute, donc personne n'est refusé à tort. Mais sur un réseau où l'on part surtout en fermant son client, elle dérive vers le haut et ne redescend qu'au prochain JOIN ou PART. À la longue, +l ne protège plus de rien — un garde-fou qui ne serre jamais est un garde-fou décoratif.
✅ PORTÉ le 04/09/2026 — il reste à RECHARGER IriX
bind sign et un second bind kick rappellent désormais ::soc::auto::do_autolimit, avec le même after 500 que le JOIN et le PART.
⚠ Le délai n'est pas un détail : Eggdrop met chanlist à jour après avoir appelé le bind. Un recalcul immédiat compterait encore celui qui vient de partir — le défaut d'origine, reproduit par l'autre porte.
⚠⚠ Le KICK avait bien le même trou. bind kick existait, vers ::soc::evt_kick, qui ne touche pas à +l. Un second bind ne remplace pas le premier : Eggdrop appelle les deux, et c'est de les nommer différemment qui le garantit.
📌 Le netsplit ne reçoit RIEN, et c'est délibéré. Eggdrop émet splt quand quelqu'un disparaît par séparation, puis sign s'il n'est pas revenu au bout de wait-split. Recalculer sur splt ferait descendre la limite pour des gens qui reviennent dans la minute, et rejn la remonterait aussitôt : deux MODE diffusés à tous les présents, pour rien. Ceux qui ne reviennent pas sont pris par sign.
Le garde-fou : IriX/tests/test-autolimit-departs.tcl, 17 constats, 6 blessures de mutation sur 6 vues. ⚠ Il n'éprouve pas l'existence des procédures — le défaut du 02/09 n'était pas une procédure manquante, do_autolimit était là et juste, c'est le chemin qui manquait. Il mesure donc qui appelle, d'où, avec quoi : chaque bind pointe sur une procédure, et le corps de cette procédure rappelle bien le recalcul. Un des constats garde une décision plutôt qu'un défaut — l'absence de bind splt : s'il rougit, la question est « a-t-on changé d'avis, et l'a-t-on écrit ? ».
✅ CONSTATÉ EN SERVICE — 04/09/2026, 15:12
IriX a été relancé à 15:04:06 (redémarrage volontaire, NRestarts=0, aucune erreur Tcl dans les cinq endroits, lien S2S remonté à 15:04:44). Le correctif a ensuite été éprouvé sur le réseau en marche, par le protocole même qui avait trouvé le défaut le 02/09 :
Sur #accueil | Limite +l |
|---|---|
| avant l'arrivée | 4 |
| un client rejoint | 5 |
| ce client QUITTE | 4 |
La limite suit désormais le départ par QUIT. ⚠ C'était le seul constat qui comptait : le banc dit que les binds sont posés, il ne dit pas qu'ils agissent. Un banc qui mesure la chose ne mesure pas son emploi.
🔴🔴 ET LE PREMIER ESSAI A FAILLI CONCLURE L'INVERSE, SUR UNE MESURE QUI N'AVAIT PAS EU LIEU. Il visait #Csadmin, qui est +i : les deux clients s'y sont fait refuser l'entrée, et le script ne s'en est pas aperçu — il attendait le 366 (fin de la liste des noms), qui n'arrive jamais sur un JOIN refusé, et rendait la main en silence après son délai. Il a lu « limite 3 » sur un salon où personne n'était entré, et allait écrire « le défaut est INTACT ».
📌 Le plancher, désormais : rejoindre() rend vrai seulement si l'entrée est constatée — notre propre JOIN qui nous revient, ou le 366 — et reconnaît les six refus (473 474 475 471 477 448). Sans entrée constatée, l'essai s'arrête au lieu de mesurer. ⚠ C'est le travers de ce dépôt, commis par le script d'essai lui-même, et sur le point qui le dénonce.
🟡 Le bruit de démarrage, à peser
16 TOPIC et 24 MODE en trois minutes après un redémarrage — dont quatre fois le même sujet sur #!Overtchat!. Chacun est diffusé à tous les présents.
⚠ C'est ponctuel : mesuré sur fenêtre calme, zéro TOPIC depuis. Deux boucles font le même travail sur la même donnée — db_topic_check toutes les 30 s et scan_topics toutes les 300 s — mais les deux comparent avant d'envoyer (IriX-sql.tcl:7423, :11836). Quatre envois signifient que la comparaison a échoué quatre fois, ce qui est cohérent avec un bot pas encore op sur un salon +t : le +o IriX n'arrive qu'à 18:43.
📌 À décider : est-ce un coût de démarrage acceptable, ou faut-il retarder la première passe jusqu'à ce que l'op soit acquis ? ⚠ Ne rien changer sans mesurer d'abord si la comparaison échoue encore une fois l'op obtenu — sinon on corrigerait un symptôme.
⚠ Un risque connexe, non mesuré
La marge est de 2 places et le recalcul part 500 ms après l'arrivée, via puthelp, la file la plus lente d'Eggdrop. Trois arrivées simultanées dans cette fenêtre → la troisième prendrait un 471. Non reproduit.
65. ✅✅ LES CINQ PSEUDO-CLIENTS ÉTAIENT DÉTRUITS ET RECRÉÉS CHAQUE MINUTE — ouvert et CONSTATÉ FERMÉ le 02/09/2026
Le point de départ était mince : dans IriX_online, host et realname vides pour les cinq services. En cherchant pourquoi, le vrai défaut est apparu.
::soc::online::reconcile compare les UID en base à ceux que la mémoire connaît et supprime la différence — des « fantômes ». ⚠⚠ Mais la liste des vivants venait de user_nick seul. Les pseudo-clients n'y figurent pas : ils n'arrivent par aucun UID entrant, c'est IriX qui les injecte. Ils étaient donc pris pour des fantômes et supprimés, puis réinsérés par le dernier passage de la même procédure.
Mesuré en production, deux lectures à 45 s :
IriX | is_svc=non | 6336 s → 6381 s (+45, normal) |
OverNick | is_svc=oui | 25 s → 10 s (reparti) |
Trois conséquences, et la première est la pire : since MENT — le panel annonce « connecté depuis 10 s » pour des services présents depuis le démarrage. Une ligne présente et fausse est pire qu'une ligne absente : elle a l'air mesurée. C'est la doctrine d'irix-online.tcl, retournée contre lui. Ensuite le ON DUPLICATE KEY UPDATE était inatteignable. Enfin, 5 DELETE et 5 INSERT par minute pour rien.
Second défaut, même procédure : l'INSERT des services s'arrêtait à vhost, alors que register_svc range host et realname dans le MÊME dict. Web/pages/live.php n'affiche le sous-titre que s'il est non vide — les services étaient les seules lignes muettes, quand « Gardien des Salons » attendait dans svc_config.
⚠ Aucun banc n'exerçait cette branche : les deux réparations de septembre ont porté sur flush, jamais sur reconcile. Une réparation s'applique à l'instance, jamais au motif.
CONSTATÉ FERMÉ après rechargement d'IriX à 17:20:09 — since passe de 65 s à 140 s (+75, exact), et les cinq realname sont remplis. Santé : 0 redémarrage, 0 erreur Tcl, 39 lignes de trafic S2S.
IriX/tests/test-online-services.tcl — 13 constats, 7 blessures, 7 rouges. ⚠⚠ Il a payé son écriture au premier lancement : ma première correction ajoutait les UID de services à vivants, et la boucle d'insertion y lisait un user_nick inexistant — « no such variable », hors catch, à chaque cycle. Deux listes séparées : « qui a le droit de rester » n'est pas « qui faut-il inscrire ».
⚠ Et deux blessures sont passées au VERT avant correction, même racine : vhost contient la sous-chaîne host, et le jeu d'essai donnait la même valeur aux deux. Un jeu d'essai où deux champs coïncident ne peut pas les distinguer. Puis j'ai durci un motif qui n'en avait pas besoin, et je l'ai cassé — il faut savoir contre quoi l'on durcit.
64. 🟡 REVUE DU SITE — il ne reste qu'une RECOMPILATION — mesuré le 02/09, refermé le 04/09/2026
Demandée par SerVuS. ⚠ Classé par ce qu'un visiteur subit, pas par ce qui est laid.
⚠⚠ Ce qui reste tient en une ligne : HELP_CHANNEL est corrigé dans la source du serveur mais n'a pas d'effet avant une recompilation, et recompiler demande l'accord de SerVuS. Tout le reste de ce point est fermé et constaté — y compris les neuf puces qui sont restées affichées comme ouvertes pendant deux jours après l'avoir cessé.
✅ Corrigé le jour même
/join #aideenvoyait les visiteurs sur un salon qui les refuse. La page Contact donnait ce salon comme « la voie la plus rapide ». Mesuré en production, pas dans les.example: le serveur déclareHELP_CHANNEL "#Help", et seul#Helpfigure dans les salons ouverts.#aideaurait rendu un 448 Cannot join channel. ⚠ Le.exampledu dépôt portait encore#aide— il est aligné, avec la raison écrite dedans.- Le chat niait TROIS fonctions qu'il possède : historique, privés en panneau dédié, reconnexion automatique. Les trois sont dans le
chat.jsservi par la même page. Une page qui promet moins qu'elle ne fait détourne des visiteurs pour rien. - Quatre liens morts dans le PIED DE PAGE — donc sur CHAQUE page. « Dépôt GitHub », « Signaler un problème » et « Licence » (deux fois) pointaient vers un dépôt privé, dont toutes les adresses web répondent 404. ⚠⚠ C'était mesuré et écrit dans ce dépôt depuis le 31/07 : la page d'installation avait retiré ses liens pour cette raison exacte, l'accueil avait corrigé son bouton. Une correction appliquée à deux pages sur trois : le défaut de famille du projet.
✅ Corrigé le 02/09/2026 — deuxième passe
Chacun est dérivé, et chacun est éprouvé par un banc que la mutation a fait rougir.
- Les quatre chiffres écrits à la main de l'accueil. « Douze onglets » →
count(panel_routes())(30) ; « environ 11 000 lignes de Tcl » → les lignes deIriX/cmds/*.tcl(31 930, arrondi au millier) ; « 280 vérifications sur neuf campagnes » → leTOTAL=querun-tests.shdéclare (19) ; « une seule commande » → quatre, comme le bloc juste en dessous. ⚠ Une aidesite_chiffre_cache()a été posée dansWeb/inc/site.php: dériver coûte, et les deux fonctions voisines avaient déjà un cache pour cette raison exacte. Elle prend des témoins de date, sans quoi un compte de lignes resterait périmé après chaque modification du Tcl. information_schemainterrogée à chaque affichage — passée par la même aide.- La page « Télécharger » ignorait le client de bureau. Une section dédiée liste les entrées de type
client, dérivée du manifeste : aucun système n'est nommé en dur, et le journal des versions en est écarté (c'est un texte, pas un programme). ⚠ Le libellé vient dedistrib_inventaire(), pas du manifeste, qui ne le recopie pas — sans quoi la colonne « Système » affichait « OverScript-installateur.exe ». Constaté en RENDANT la page ; le repli?? $f['nom']a l'air parfaitement correct dans le code source. - Deux téléchargements présentés comme différents. La page compare désormais les empreintes et dit ce qu'elle a mesuré. ⚠ Écrire « c'est le même fichier » aurait remplacé une affirmation fausse par une affirmation vraie aujourd'hui : le jour où les deux entrées divergeront, la page mentirait de nouveau.
- La pastille menait à une page qui ne parle pas de mise à jour. ⚠⚠ Et c'est plus retors que décrit : le point 32 avait fait CONVERGER les deux liens vers
?p=statut— la convergence était juste, la cible était fausse, et leur accord l'a rendue invisible deux semaines. Mesuré :update_verdict()n'est appelé que parlayout.phpetdashboard.php. La pastille vise maintenant?p=dashboard#maj; le badge du tableau de bord cesse d'être un lien qui ramène où l'on est. - Zoom iOS du panel. ⚠⚠ Le commentaire de la feuille ET le banc défendaient le défaut. Tous deux affirmaient « pas de
!importantici : ces champs tiennent leurs dimensions de la feuille, pas d'un attributstyle». La prémisse est vraie, la conclusion ne suit pas : le danger était une règle plus spécifique de la même feuille (.mode-param input, 12 px). Le constat detest-vues.phpne dit plus « aucun!important» — il mesure ce qui le rend superflu ou nécessaire. - Le tableau de 8 colonnes de
?p=statuta son.tbl-wrap. - Les champs sans
label. ⚠ Trois étaient nommés dans ce registre ; les corriger un par un aurait laissé les suivants.Web/tests/test-champs-nommes.phpbalaie toutes les pages — 44 constats, dont un plancher. - Les trois comptes du même chiffre dans
admin.php, avecWeb/tests/test-comptes-services.php(8 constats). ⚠⚠ Le repli?: 5était le pire des trois : il fabriquait un dénominateur quand la liste est vide, et « 0 / 5 » se lit « cinq attendus, aucun n'est là » — une panne — alors que la vraie phrase était « la liste n'a pas pu être lue ».
⚠⚠ UN POINT DE CE REGISTRE DISAIT FAUX. Il annonçait : « .raison.info n'existe pas : une raison de niveau inconnu s'affiche avec une puce invisible ». Mesuré dans Web/site/compatibilite.php : .raison::before porte déjà background: var(--dim). Tout niveau inconnu rend une puce grise, et rien n'est invisible. Aucun code n'a été touché.
✅ Corrigé le 02/09/2026 — troisième passe
- Le nom du service était gravé 21 fois dans 13 fichiers — le registre disait « onze pages », mesuré : 13 fichiers, 21 occurrences vivantes. Une aide
nick_service_nom()rend le nom seul, etsite.phpla charge pour toutes les pages. ⚠⚠chat.phpse contredisait à deux endroits, pas un. ⚠ Le pire cas n'était pas une page mais le corps d'un courriel de réinitialisation, qu'on ne peut plus corriger après l'envoi. BancWeb/tests/test-nom-du-service.php. - La table des cinq services de
cmd_services()dérive desvc_config. ⚠⚠ Les deux concordaient parfaitement — c'est exactement ce qui rend le défaut durable : une table recopiée qui dit vrai ne se remarque pas. BancWeb/tests/test-cmd-services.php. - Les salons d'accueil. ⚠⚠ Le registre parlait de « deux salons concurrents » ; la mesure dit autre chose.
config.example.phpest cohérent — il déclareWEBCHAT_CHANS_Rpour#membres, qui rend un 477 aux visiteurs anonymes et se joint après identification. Le vrai défaut était un commentaire faux :#accueilétait donné pour injoignable alors qu'il répond parfaitement ;#aide, cité dans la même phrase, était bien refusé. Une moitié juste, une moitié fausse. 📌IriX/tests/essai-salons-webchat.pytranche désormais en le DEMANDANT au serveur.
⚠ Le commentaire fautif vit dans Web/inc/config.php, HORS DÉPÔT (.gitignore:126) : la correction ne voyage pas. C'est config.example.php, lui distribué, qui porte la mesure. Le fichier de production garde son commentaire faux tant que personne ne l'édite sur la machine.
✅ Config/unrealircd/salons.conf — MESURÉ le 02/09/2026 : plus rien ne le lit
Trois chemins indépendants, tous concordants.
Le serveur ne le lit pas. Il tourne depuis /home/servus/overtchatd/, pas depuis le dépôt : overtchatd -f -c /home/servus/overtchatd/conf/overtchat.conf. Cette configuration n'a qu'un include, vers conf/opers.conf, et aucune référence à Config/. Côté code, grep -rn "Config/" sur tout le C et les .conf du serveur rend zéro.
La vraie source est conf/salons-interdits.conf, chargé par la directive banned-channels (src/conf.c:1503, badchans_load()), avec deux listes chaînées — interdictions et exceptions, ces dernières consultées EN PREMIER. Sa zone d'exceptions est régénérée depuis la base par Tools/salons-exceptions.sh --appliquer.
Aucun PHP ne l'ouvre. Les six références sont toutes des commentaires ; aucun file_get_contents ni fopen ne le vise.
⚠⚠ ET IL EST PÉRIMÉ, CE QUI EST PIRE QUE MORT. Il porte 5 exceptions quand la configuration en service en a 7 : #!Bots! et #Help y manquent — or #Help est bien joignable, mesuré. Le consulter pour savoir ce qui est ouvert induit en erreur. Un fichier qu'on ne lit plus mais qui décrit encore est plus trompeur qu'un fichier absent : il a l'air d'une source.
Corrigé : les commentaires de chanreq.php (deux) et demandes.php prescrivaient de poser l'exception « à la main » dans ce fichier — un geste mort. Ils nomment maintenant Tools/salons-exceptions.sh --appliquer. ⚠ Le message AFFICHÉ était déjà juste ; c'est la documentation interne qui mentait, à un développeur qui n'a aucune raison de la vérifier.
⚠ Pourquoi aucun banc ne l'avait vu : test-consignes.php interdit bien ces chemins, mais il retire les commentaires avant de chercher — le remède appliqué partout ailleurs crée ici l'angle mort. Les docblocks lui sont invisibles.
✅ FAIT le 02/09/2026, sur décision de SerVuS : Config/unrealircd/ est retiré du dépôt — les trois fichiers, chacun mesuré sans lecteur. websocket.conf : le WebSocket se déclare en ligne dans la configuration du serveur. ircd.motd : le vrai modèle est Servers/overtchat-hybrid/conf/ircd.motd.tpl, dont Tools/motd-generer.php fabrique le fichier servi. ⚠ Le motif d'exclusion de l'installateur est conservé : une installation d'avant cette version porte encore le dossier.
⚠ Ne PAS confondre avec Components/Serveur/bin/unrealircd/, qui porte les certificats TLS employés par le serveur actuel — les supprimer casserait le TLS.
🟡 CORRIGÉ EN SOURCE le 02/09, il attend une recompilation — le serveur annonce un salon qui refuse
⚠ Le titre disait « NON CORRIGÉ » deux jours de trop. Mesuré le 04/09 : conf.c porte désormais "" comme défaut, avec l'encadré qui explique le choix du vide, et m_misc.c:250 teste if (*feature_str("HELP_CHANNEL") != '\0') avant d'annoncer quoi que ce soit. Le garde-fou est Tools/tests/test-salon-aide.sh. Ce qui reste n'est plus « corriger » mais « recompiler », et cela demande l'accord de SerVuS — c'est le seul reste de ce point.
Ce qui suit décrit l'état d'AVANT le correctif, et le pourquoi.
Servers/overtchat-hybrid/src/conf.c portait alors :
{ "HELP_CHANNEL", FEAT_STR, 0, "#aide", ... }
Le défaut compilé était #aide. La production déclare HELP_CHANNEL "#Help" et n'ouvre que celui-là ; #aide rend un 448, mesuré le 02/09.
⚠⚠ Toute installation neuve qui omet HELP_CHANNEL annonce donc à chaque nouveau venu un salon d'assistance qui le refuse — src/cmd/m_misc.c:250 l'affiche dans « Assistance : … ». C'est exactement le défaut corrigé le matin même sur la page Contact, mais dans le serveur, et il touche les installations qu'on ne voit jamais.
⚠ Non corrigé volontairement : changer une valeur en dur du C n'a d'effet qu'après recompilation, ce qui demande l'accord explicite de SerVuS. Corriger la source sans recompiler aggraverait l'écart binaire/sources que run-tests.sh refuse déjà.
✅ Chiffres écrits à la main — FERMÉ, constaté le 04/09/2026
Les quatre sont dérivés, et trois passent par un cache : $nb_routes, $nb_tcl, $nb_campagnes et $nb_tables viennent tous de site_chiffre_cache(...) (Web/site/accueil.php). ⚠ Les phrases fautives ne subsistent que dans le commentaire d'en-tête, qui raconte le correctif — et c'est ce qui a failli les faire recompter comme un défaut.
Ce que la page annonçait avant, pour mémoire :
| Ce que la page annonce | Mesuré |
|---|---|
| « Douze onglets pour piloter la plateforme » | 30 routes dans panel_routes() |
| « Environ 11 000 lignes de Tcl » | 31 871 hors bancs |
| « 280 vérifications sur neuf campagnes » | le lanceur déclare 19 campagnes |
| « Une seule commande pour commencer » | le bloc en montre quatre |
✅ LES NEUF POINTS DE CETTE SECTION SONT FERMÉS — constaté le 04/09/2026
⚠⚠ Et ils l'étaient depuis les 2ᵉ et 3ᵉ passes du point, sans que personne le note. Cette liste est restée telle quelle pendant que le code la démentait, ligne par ligne. C'est le défaut que ce registre reproche aux autres documents, commis par le registre lui-même.
| Ce que la liste disait ouvert | Ce que le code répond |
|---|---|
| deux téléchargements présentés comme différents servent le même octet | telechargement.php le dit maintenant lui-même : « la page vient de comparer les deux empreintes, et elles sont identiques » |
| la pastille « v… disponible » mène à une page qui n'en parle pas | layout.php → ?p=dashboard#maj, avec le motif du changement écrit juste au-dessus |
| trois comptes du même chiffre, dont deux en dur | Web/tests/test-comptes-services.php ; le « /5 » n'est plus qu'un commentaire d'historique |
| « OverNick » écrit en dur dans onze pages | nick_service_nom() (Web/inc/nickservice.php), gardée par test-nom-du-service.php |
| deux salons d'accueil concurrents dans trois sources | WEBCHAT_CHANS les porte ensemble — ce n'était pas une contradiction mais une liste, et la mesure l'a tranché en le DEMANDANT au serveur |
zoom iOS sur le panel : la règle des 16 px sans !important | elle l'a. ⚠ Mais le défaut était réel et ailleurs — voir ci-dessous |
un tableau de 8 colonnes sans conteneur défilant (?p=statut) | quatre .tbl-wrap dans statut.php |
trois champs sans label | Web/tests/test-champs-nommes.php |
information_schema interrogée sans cache à chaque affichage | site_chiffre_cache('accueil-tables', 3600, …) |
🔴🔴 LA PUCE DU ZOOM iOS AVAIT RAISON SUR LE SYMPTÔME ET TORT SUR LA CAUSE, et c'est ce qui l'a maintenue vraie deux semaines de plus. Elle accusait un !important manquant. Il ne manquait pas. Ce qui manquait, mesuré le 04/09 sur les 30 pages du panel à 375 px : le sélecteur de la règle énumérait les types un par un (input[type=text], …), et 26 champs du panel n'ont pas d'attribut type — le navigateur leur donne « text », le sélecteur d'attribut ne les voit pas. 51 champs restaient à 13,3 px, sur 6 pages.
📌 Une cause fausse referme la question : on lit « !important manquant », on constate qu'il est là, et l'on classe la puce comme périmée sans jamais rouvrir le navigateur. Corrigé, et gardé par Web/tests/test-panel-mobile.php, éprouvé par mutation (5 blessures sur 5 le font rougir). Le récit complet est dans MOBILE.md.
✅ La contradiction sur les dépôts est tranchée — 03/09/2026
index.php disait « les dépôts sont privés », installation.php disait « git clone fonctionne en anonyme ». installation.php porte désormais l'aveu : « le dépôt est PRIVÉ… était FAUX », et les deux pages s'accordent.
63. 🔨 SVSMODE n'est reflété NULLE PART — mesuré le 02/09/2026, PORTÉ le 02/09/2026
Ce que c'est. Un mode utilisateur posé par un service — le +r de l'identification, un -o retiré à un opérateur — n'atteint aucune table d'IriX. Ni <bot>_online.modes, ni <bot>_users.is_oper.
⚠⚠ Et ce n'est PAS le mécanisme habituel du point 56. SVSMODE est bel et bien DIFFUSÉ sur le lien (sendto_server, m_service.c:189) — le dispatcher S2S le reçoit. Mais il n'a aucune branche pour lui : la ligne tombe dans le default, qui écrit « CMD: SVSMODE (ignoré) ».
📌 Le piège est dans le serveur, et il est net : quand un service pose un mode, le démon appelle send_umode_change(…, false) (m_service.c:187), et s_user.c:703-705 ne propage le MODE aux serveurs que si ce drapeau est vrai. Donc le MODE équivalent ne circule pas : SVSMODE est la SEULE trace, et personne ne la lit.
⚠⚠ Ce défaut touche le correctif du point 58 (is_oper, 01/09). refleter_oper suit les branches MODE et UMODE2 ; un -o posé par un service lui échappe entièrement, et la colonne reste à oui jusqu'au départ de la personne.
⚠ Et la table des exceptions du banc d'inventaire dit FAUX à son sujet : test-verbes-atteignables.tcl porte la raison « SVSMODE : services vers serveur ; l'effet revient en MODE ». Mesuré : il ne revient pas. C'est exactement le défaut que ce banc existe pour combattre — une raison raisonnée, jamais mesurée. La corriger fait partie du correctif.
Pour le fermer : une branche SVSMODE - SVS2MODE au dispatcher S2S qui recompose les modes et appelle ::soc::online::on_modes, comme le fait déjà la branche MODE. ⚠ SVS2MODE compte autant que SVSMODE — ils ne diffèrent que par le fait que la personne voit ou non le changement.
Ce qui est PORTÉ, le 02/09/2026 — et ce n'est pas la même chose que fermé :
- la branche
SVSMODE - SVS2MODEau dispatcher S2S, qui recompose l'état complet et appelle::soc::online::on_modeset::soc::operjoin::sur_modes; - une procédure
::soc::ulines::auto::recomposer_umodes, partagée avec la brancheMODEqui portait la même boucle en clair — un delta appliqué à deux endroits diverge un jour ; - la raison fausse de
test-verbes-atteignables.tclcorrigée ; IriX/tests/test-svsmode.tcl, 24 constats, qui EXÉCUTE la branche au lieu de la relire, et 11 blessures de mutation, 11 rouges.
IriX A RECHARGÉ le 02/09/2026 à 15:20:29, à la demande de SerVuS. Le fichier datait de 14:47:49 : le processus a donc bien lu la version neuve. Redémarrage sain — service actif, zéro redémarrage (pas de boucle d'échec), zéro erreur Tcl dans les quatre sources consultées, lien S2S vivant (16 lignes de trafic sur 90 s, deux EOS, un MODE traité), et IriX_online repeuplée à 8 lignes.
⚠⚠ ET POURTANT CE POINT RESTE OUVERT — pour une raison qui a CHANGÉ. Ce n'était plus « IriX n'a pas rechargé » : c'est que rien n'émet de SVSMODE sur ce réseau. Anope est inactive, et le serveur exclut la source de sa rediffusion (sendto_server(source->from, …)) — les SVS2MODE qu'IriX émet lui-même ne lui reviennent donc jamais. Mesuré après le redémarrage : les seuls verbes tombés dans le default sont les numériques 312, 313, 319, 320 et 378. Aucun SVSMODE, parce qu'aucun n'est arrivé.
📌 Le constat manquant demande un SECOND service lié — c'est exactement ce que test-svsmode.tcl annonce ne pas pouvoir éprouver. ⚠ Le monter n'est pas anodin : le réseau WSL d'essai est en mirrored, et tous les ports de la production sont déjà pris. À décider avec SerVuS, pas seul.
⚠ Un défaut connu subsiste, et il est hors de cette branche : la garde #* protège des SVSMODE de salon, mais la première écriture du banc ne l'éprouvait pas — le cas jouait un salon sans entrée dans user_modes, et le info exists voisin suffisait à tout arrêter. La garde pouvait être retirée sans qu'une ligne rougisse. Trouvé par mutation, corrigé le jour même. Un constat qu'on n'a jamais vu rougir est une intention, pas une garde.
62. 🔨 OverScript — dix demandes de SerVuS, le 02/09/2026
⚠⚠ CE POINT EST RESTÉ FIGÉ JUSQU'AU 04/09/2026, alors que six des dix avaient bougé. Il portait encore ses dix lignes en 🔨, sans un mot d'avancement, quatre versions après. C'est exactement ce que ce registre reproche aux autres documents : une affirmation d'état qui vieillit sans que personne la touche.
🔴🔴 ET IL A RECOMMENCÉ LE JOUR MÊME, EN DEUX HEURES. Le tableau a été mesuré à 10:46 ; les cinq chantiers de la 0.6.15 ont été portés entre 11:56 et 13:12, et la ligne (1) est devenue fausse sans que personne y touche. 📌 La leçon n'est pas « il faut être plus attentif » — c'est que tout tableau d'état porte sa date de péremption dans son écriture même. Celui-ci dit désormais quand il a été mesuré, ligne par ligne, plutôt que d'affirmer qu'il est à jour.
⚠ Une seule était un DÉFAUT ; les neuf autres sont des ajouts. Le distinguer change l'ordre : ce qui est cassé passe avant ce qui manque.
✅ LE DÉFAUT — corrigé le 02/09/2026. Dans le fil de discussion, les pseudos qui portent @, % ou + s'affichaient sans leur symbole, alors que la liste des présents les montrait correctement. Deux lectures de la même information qui divergent — le motif que ce projet paie le plus souvent. Le rendu pose désormais msg-signe à côté du pseudo (src/renderer/app.js).
Les neuf ajouts, tels que demandés :
| Demande | État mesuré le 04/09/2026 | |
|---|---|---|
| 1 | Une croix pour fermer l'onglet Serveur — il reste ouvert après une connexion réussie | ✅ posée le 02/09, commit 93efb25 — salon-fermer-reseau (app.js), qui déconnecte et retire tous ses salons. Et le cadre Serveur se replie en plus depuis la 0.6.15. 🔴 Mon relevé du 04/09 disait « pas de croix » : il était FAUX, et le commit qui la pose s'appelle « Fermer un reseau » |
| 2 | Un interrupteur « Couleur Texte : On/Off » à côté d'Aide | ✅ posé le 02/09 · et il se retient d'un lancement à l'autre depuis la 0.6.13 |
| 3 | Les barres colorées autour de « Service-Overtchat » : les animer en défilé | ✅ faites le 02/09, commit 93efb25 — .banniere-trait + @keyframes banniere-defile (5,5 s, la seconde en sens inverse), avec repli sous prefers-reduced-motion. 🔴 « rien trouvé » était FAUX |
| 4 | Un interrupteur « Mise à Jour : On/Off », piloté par le MÊME réglage que la case du panneau | ✅ posé le 02/09 · les deux vues sont bien liées · il se retient depuis la 0.6.13 |
| 5 | Un menu déroulant par serveur listant les salons ouverts | 🟡 refusé par écrit, pas oublié — un CHEVRON replie les salons sous leur tête de réseau (app.js, 02/09). Le motif est dans le code : « la liste EST déjà l'arborescence ; un second menu ferait deux endroits où chercher, et deux à tenir d'accord ». À valider par SerVuS — c'est un choix, pas un manque |
| 6 | La fenêtre List ne doit plus se fermer quand un salon s'ouvre : fermer / agrandir / réduire | ✅ — la fenêtre « annuaire » a ses trois gestes : annuaire-reduire et annuaire-agrandir (app.js) posés le 02/09, plus Fermer. 🔴 « ni agrandir, ni réduire » était FAUX |
| 7 | Une fenêtre Partyline dédiée par eggdrop | 🔨 rien trouvé |
| 8 | Sécurité : bloquer flood / CTCP contre le script ou le pseudo | ⚠ à demi — le nombre de fenêtres privées est plafonné (PRIVES_MAX = 60), et aucun CTCP ne reçoit de réponse automatique (« répondre renseigne un inconnu sur le poste et sert à pister »). Rien ne borne en revanche la CADENCE des messages |
| 9 | Une barre animée montrant la RAM, avec alerte si le poste sature | ✅ posée le 02/09 · et elle PARLE depuis la 0.6.13 : mise en garde à 70 %, alerte à 80 %, plus une garde sur notre propre part au-delà d'1 Go |
📌 Neuf sur dix ont bougé — mesuré le 04/09 à 15:40, et ce chiffre-ci est le TROISIÈME : six entièrement (1, 2, 3, 4, 6, 9), une à demi (8), une tranchée par un choix de conception à valider (5), une intacte (7).
🔴🔴 ET LA MESURE DE 13:12 ÉTAIT FAUSSE SUR TROIS LIGNES — (1), (3) et (6), déclarées « rien trouvé » ou « à demi » alors que le code était posé depuis le 02/09, dans un commit unique, 93efb25, dont le titre dit « Fermer un reseau, lire les couleurs, deux interrupteurs ». Je n'ai pas cherché le geste, j'ai cherché le mot.
⚠⚠ C'est le travers symétrique de celui que ce point dénonce plus haut. Un tableau d'état peut mentir dans les deux sens : dire fait ce qui ne l'est pas, et dire manquant ce qui existe. Le second coûte plus cher — il fait réécrire ce qui marche, et le doublon masque l'original. 📌 Le remède tient en une ligne : avant d'écrire « rien trouvé », faire un git log -S "<le geste>" sur le fichier. Trente secondes, et le titre du commit répond.
⚠ Ce que la 0.6.15 a apporté n'est PAS dans cette liste : le clic droit sur un pseudo, la fenêtre des nouveautés et l'écriture de la mise en forme sont des demandes du 04/09, arrivées par les rapports du panel. ⚠⚠ Et la barre de mise en forme n'est pas la demande (2) : celle-là décide si l'on VOIT les couleurs des autres, celle-ci de ce qu'on ENVOIE. Les confondre ferait croire le point plus avancé qu'il n'est.
⚠ Ne restent vraiment que (7) et (8), et ce sont les deux plus gros, et le (8) est le seul qui touche à la sécurité — un filtre trop large rendrait le client sourd à des messages légitimes. ⚠⚠ Sa moitié faite est la passive (ne pas répondre, ne pas laisser proliférer) ; sa moitié manquante est l'active (limiter une cadence), et c'est celle qui peut faire taire quelqu'un à tort.
61. ✅ Deux numériques s'affichaient bruts À CHAQUE ENTRÉE dans un salon — CONSTATÉ FERMÉ le 01/09/2026 sur Libera.Chat
Ce que c'est. Même famille que le PONG du 31/08 : une réponse du serveur qu'aucune branche ne prend en charge, et qui tombe dans l'affichage tel quel — au milieu du salon.
| Code | Ce qu'il porte | Ce que la personne lit |
|---|---|---|
| 329 | l'heure de création du salon | un horodatage brut, une fois par /join |
| 470 | le RENVOI vers un autre salon | un numérique, alors qu'elle vient d'atterrir ailleurs |
⚠⚠ Le 470 est le plus coûteux des deux. /join ##chat sur Libera dépose réellement dans ##chat-overflow — constaté. La personne se retrouve dans un salon qu'elle n'a pas demandé et rien ne le lui dit en français. Elle croit être où elle voulait.
Comment ç'a été trouvé — bancs/veille-reelle.js, 20 min sur ##chat, #python, #linux : 26 messages vus, zéro erreur de transport, et 18 lignes brutes toutes arrivées dans les 12 premières secondes. Rien du trafic courant n'échappe au traitement.
📌 Ce que la veille ÉLIMINE compte autant que ce qu'elle trouve : le PONG était un cas isolé, pas la partie visible d'une famille qui déborderait en conversation. Sans cette mesure on aurait pu « corriger » longtemps au hasard.
⚠ Réserve à garder en tête : trafic mince à cette heure (26 messages en 20 min). Une veille en pleine journée éprouverait davantage. Un banc qui n'a pas vu le défaut ne prouve pas son absence.
⚠ Le harnais avait lui-même deux défauts, corrigés depuis : sa colonne de forme affichait la même valeur pour toutes les lignes (elle lisait params, que cette charge ne porte pas) — donc elle ne mesurait rien tout en se lisant comme une mesure ; et il affichait le nom du réseau à l'état pret, émis sur le 001, qui précède le 005 — un null légitime qui faisait soupçonner un défaut inexistant.
✅ CONSTATÉ FERMÉ le 01/09/2026, sur Libera.Chat
Les deux numériques entrent dans FORMES (src/main/irc/numeriques.js). Constaté sur le réseau vivant, pas seulement au banc — c'est la seule preuve qui vaille ici, le défaut n'ayant jamais été visible autrement :
470 type=erreur
« ##chat vous a renvoye vers ##chat-overflow : vous n etes PAS dans ##chat »
329 type=info
« ##chat-overflow existe depuis le 19/05/2021 a 14:43 »
📌 Le 470 nomme les DEUX salons, et dans le bon sens. « renvoyé vers ##chat » serait aussi lisible et exactement faux ; un constat garde le sens, pas seulement les noms.
5 constats neufs dans bancs/numeriques.test.js (30 au total), 9 blessures toutes mordantes.
⚠⚠ Deux défauts trouvés en chemin, et aucun des deux n'était celui qu'on cherchait :
- Un commentaire du dépôt disait l'INVERSE du code, et m'a fait écrire le défaut. L'en-tête de
FORMESannonçait « rend un texte — ounullpour tomber sur le repli ». C'est faux :nullveut dire ne rien afficher (c'est ainsi que le 318 se tait), et le repli s'obtient par une chaîne vide. Mes deux gabarits rendaient doncnullsur une ligne malformée — ils la perdaient en silence, précisément ce que tout ce fichier existe pour empêcher. 📌 L'explication détaillée, vingt lignes plus bas, était juste. Une explication juste enterrée sous un résumé faux ne protège personne. - Le 470 prenait le TEXTE du serveur pour un nom de salon. Sur
470 <nous> ##seul :Forwarding to another channel, un simplea[0] && a[1]écrivait « ##seul vous a renvoyé vers Forwarding to another channel » — une phrase bien formée, en français, et entièrement fausse. ⚠ Trouvé par la mutation, sur un cas d'essai que j'avais écrit en croyant tester autre chose. Les deux paramètres doivent être des salons (estSalon), sinon on retombe sur le repli.
60. 🟡 Modération dans OverScript — LE LECTEUR EST POSÉ le 04/09/2026, rien n'est branché
✅ Ce qui existe depuis le 04/09/2026
src/main/moderation.js lit la liste, normalise, et répond à une seule question : ce message contient-il un mot de la liste, et de quelle gravité ? 12 constats, 8 blessures de mutation sur 8 vues.
🔴 Il ne sanctionne personne, et un constat du banc le garde : il lit son propre source et refuse d'y trouver KICK, MODE ou un envoi. Rien n'est branché — le comportement du client est inchangé, et actif_par_defaut reste false.
⚠⚠ ET LE PREMIER RÉFLEXE AURAIT ÉTÉ FAUX. Avant d'écrire une ligne, la question de la règle 5 : par quel mécanisme ce fichier voyage-t-il ? Mesuré — il ne voyage pas. electron-builder.yml porte une liste fermée de ce qui entre dans le paquet, et Config/ n'y figure pas. Un module qui aurait lu Config/moderation-mots.json aurait marché parfaitement dans le dépôt, tous bancs verts, et jamais chez personne.
📌 D'où deux emplacements, le motif d'identity.json.example : la liste de départ voyage sous src/main/moderation-liste.js, engendrée depuis le JSON (Tools/engendrer-liste-moderation.js — dérivée, jamais recopiée) ; la liste de l'utilisateur vit dans son dossier de données, où elle est éditable, le paquet étant en lecture seule.
⚠ Une huitième blessure a d'abord fait un TROU, et la cause était un vrai défaut : le drapeau i de l'expression régulière rendait le .toLowerCase() inutile. Deux gardes pour la même chose, dont l'une masquait l'autre — une garde masquée est une garde qu'on croit éprouvée.
✅ Le branchement est fait — 04/09/2026, publié en 0.6.16
Quatre garde-fous, et chacun porte sa blessure : éteinte par défaut · jamais en privé (on n'y a aucun pouvoir) · jamais ses propres messages · seulement là où l'on porte un grade.
⚠⚠ Ils vivent dans moderation.avisPour, PAS dans le pont — et c'est la seule raison pour laquelle ils sont éprouvés. Le premier jet les gardait au milieu d'un écouteur d'événement de pont.js : rien ne pouvait les mesurer. Sortis dans une fonction qui reçoit des faits, ils portent quatre constats et quatre blessures. Un banc qui mesure la chose ne mesure pas son emploi — et l'emploi, ici, c'est la suite des refus.
⚠ Le signe se DEMANDE à la session (monSigneDans, extrait de _vueSalon), il ne se recalcule pas : deux implémentations de la même règle divergent, et celle-ci a déjà fait afficher « sans grade dans ce salon » à un opérateur. ⚠⚠ Et '' est un signe IRC valide — « présent sans grade » — qu'il faut refuser explicitement : un == null le laisserait passer.
Ce que voit l'utilisateur : un interrupteur « Modération : Off » et, quand il l'allume, une ligne dans le salon concerné — « ⚠ modération — Quidam : « … » (insulte, gravité 2). Rien n'a été envoyé. Pour agir : /mode #salon -v Quidam ».
🔴 AUCUN BOUTON, ET C'EST LA DÉCISION DU POINT. Taper la commande reste un geste, et ce geste est le garde-fou. On découvre ainsi le taux de faux positifs avant de rendre l'action facile. ⚠ Le titre de l'interrupteur dit que rien ne part sans vous, et un constat l'exige : un interrupteur nommé « Modération » se comprend spontanément comme « le script va agir à ma place ».
📌 Ce qui reste à trancher par SerVuS : l'automatique. Il ne se posera utilement qu'après avoir vu ce que la liste repère sur de vraies conversations.
✅ PUBLIÉ en 0.6.16 — constaté depuis l'extérieur le 04/09/2026
Les cinq fichiers déposés à 16:00, le numéro annoncé à 16:02. Les trois paquets sont servis avec une empreinte identique au binaire local (Windows 91,0 Mo · AppImage 129,3 Mo · deb 101,9 Mo), et la section « ## 0.6.16 » figure dans le journal servi.
Web/tests/test-overscript.php, sur la machine qui sert : 52 constats, 0 échec — dont « le numéro annoncé par le site EST celui du binaire construit », « le binaire servi est bien celui que ce latest.yml décrit », et « tout paquet déposé est annoncé dans plateformes ».
⚠ L'installation locale n'a PAS été faite : OverScript vit dans C:\Program Files, et y installer demande une élévation. Un /S lancé sans droits échoue en silence — la version installée est restée en 0.6.15, intacte. Poser une invite UAC sur la machine de SerVuS en son absence n'était pas à faire. 📌 Sans installation, le point 59 ne peut pas être constaté sur l'application empaquetée.
Mesuré : 19 constats sur le module, 13 sur l'interface, 15 blessures de mutation sur 15 vues.
⚠⚠ Ce qui doit être tranché par SerVuS avant tout automatique : une expulsion décidée par une liste de mots frappe une personne réelle sur un faux positif. Le fichier de mots le dit lui-même — « Une expulsion sur un faux positif coûte plus cher que le message qu'elle empêche » — et il énumère ce qu'une liste ne peut pas voir : la citation (« il m'a traité de connard » est un signalement), l'ironie entre gens qui se connaissent, les déformations, et la phrase blessante qui n'emploie aucun de ces mots.
60bis. 🔨 La demande d'origine, telle que formulée — 31/08/2026
Ce qu'il demande. Un service de modération dans le client : repérer les mots incorrects et les insultes, et proposer d'activer le traitement automatique au moment où l'on devient @ sur un salon — pour que le script agisse sans qu'on ait à le faire à la main.
📌 Le moment choisi est le bon. Devenir opérateur est exactement l'instant où la question a un sens : avant, on n'a aucun pouvoir d'agir ; après, on en a un et on ne s'en sert pas toujours. La proposition arrive quand elle est utile, et elle se refuse.
⚠⚠ CE QUI EST TRANCHÉ DÈS MAINTENANT : LA LISTE NE SE RÉCOLTE PAS.
L'idée est venue pendant une veille sur des salons publics de Libera.Chat, et il serait facile d'en tirer un lexique. On ne le fera pas. Ces personnes ne savent pas qu'un client d'essai écoute ; retenir leurs messages pour en dériver quoi que ce soit serait les collecter sans raison ni accord. La veille (bancs/veille-reelle.js) est écrite pour ne garder que des verbes de protocole, et son en-tête dit pourquoi.
La liste appartient donc à l'exploitant : un fichier qu'il ouvre, remplit et corrige. Trois conséquences de forme :
| Point | Ce qui en découle |
|---|---|
| Origine | un fichier local, éditable, vide par défaut — pas de lexique embarqué |
| Portée | par salon, pas globale : ce qui passe ailleurs ne passe pas partout |
| Réversibilité | l'automatique se coupe d'un geste, et dit ce qu'il a fait |
Ce qui reste à trancher, et qui n'est pas technique :
- ⚠ Que fait le traitement automatique ? Avertir, retirer la voix, expulser, bannir — l'échelle n'est pas la même, et une expulsion automatique sur un faux positif coûte plus cher que le message qu'elle empêche. 📌 Commencer par le degré le plus faible qui serve à quelque chose.
- ⚠⚠ Un mot n'est pas une intention. Une correspondance simple frappe les citations, les pseudonymes, et les mots contenus dans d'autres. Le projet a déjà payé ce piège côté serveur — voir la modération des messages privés.
- Qui voit la trace ? Une action automatique sans journal lisible est une action que personne ne peut contester.
⚠ À ne pas confondre avec la modération du SERVEUR (Markdown/, filtre C, annonce) : celle-ci vit dans le CLIENT, ne vaut que pour les salons où l'on est @, et ne protège que soi — un autre client du même salon n'en bénéficie pas. C'est une commodité d'opérateur, pas une politique de réseau.
59. 🔴 LA FENÊTRE UNIQUE N'EST CONSTRUITE QU'À MOITIÉ — mesuré le 31/08/2026, APRÈS publication
Ce que c'est. La 0.5.0 a été publiée en annonçant « plusieurs réseaux dans une seule fenêtre ». C'est faux. L'affichage sait grouper plusieurs réseaux ; rien ne peut en créer un second.
ponts = Map( webContents.id → UN Pont ) ← une fenêtre, une entrée
Pont.session = une seule session ← remplacée à chaque connexion
Une fenêtre porte donc exactement une connexion. /server -m ouvre une nouvelle fenêtre parce que c'est la seule chose que le client sache faire — nouvelleFenetre() est le seul chemin, et il crée fenêtre + pont ensemble.
⚠⚠ CE QUE LA « PREUVE » PROUVAIT EN RÉALITÉ. J'ai écrit, en fusionnant : « Constaté à la capture : ReseauA/#chez-a et ReseauB/#chez-b, chacun sous sa tête de groupe. » C'était vrai — et sans rapport. bancs/rendu.js fabrique les événements avec des conn inventés : il éprouve le GROUPEMENT, jamais la voie qui produirait deux connexions réelles. 577 constats verts, et pas un seul ne touchait le manque.
📌 C'est le motif du 31/08, à sa pire occurrence : non plus du code mort découvert dans l'existant, mais une fonctionnalité publiée dont la moitié n'existe pas — et une capture d'écran qui donnait l'illusion du contraire. Une preuve qui montre la chose ne montre pas son emploi.
Symptômes rapportés par SerVuS le soir même, tous deux exacts et tous deux inévitables :
| Ce qu'il tape | Ce qui arrive, et pourquoi |
|---|---|
/server <hôte> | pré-remplit le panneau de CETTE fenêtre — se connecter remplace la session en cours |
/server -m <hôte> | ouvre une nouvelle fenêtre, avec deux lignes « Serveur » : la tête locale et celle du nouveau réseau |
Pour le fermer — le travail est dans le processus PRINCIPAL, pas dans l'affichage :
- ✅
pontsdoit associer une fenêtre à plusieurs ponts (ou un pont à plusieurs sessions), etpontDe(ev)choisir lequel — vraisemblablement par l'idConnexionque le rendu porte déjà. - ✅ Un canal
irc:connecterqui ajoute au lieu de remplacer. - ✅
/server -mbranché dessus au lieu denouvelleFenetre. - ✅ ⚠⚠ Et un banc qui parte de la commande, pas de l'affichage :
/server -mdoit produire DEUX sessions vivantes dans une fenêtre. C'est exactement le constat qui manquait.
⚠ Ne pas retirer nouvelleFenetre : deux fenêtres restent légitimes (deux écrans). C'est le défaut par lequel -m passe qui doit changer, pas la possibilité.
✅ LES QUATRE SONT FAITS — 01/09/2026
Les (1) et (2) l'étaient depuis le refactor multi-réseau : Pont.liens est une Map, et la ligne if (this.connexion) this.deconnecter(); a quitté la tête de connecter(). Les (3) et (4) ont été faits ce jour.
⚠⚠ -m A CHANGÉ DE SENS, et c'est une conséquence, pas un choix esthétique. Depuis que valider le panneau AJOUTE un réseau, -m ouvrait une fenêtre pour obtenir ce que la fenêtre courante savait déjà faire — deux lignes « Serveur » dont l'une était la tête locale d'une fenêtre vide. Il pré-remplit désormais le panneau de CETTE fenêtre. La seconde fenêtre n'est pas perdue : elle prend le drapeau -w, servie par nouvelleFenetre inchangée, et un constat la garde — sans lui, cette voie serait devenue morte sans que personne s'en aperçoive.
📌 Le banc part de la commande et va jusqu'au bout : il tape /server -m, vérifie qu'aucune fenêtre n'est demandée, lit le panneau pré-rempli, le valide, et regarde ce qui part. Le constat qui porte le défaut vécu par SerVuS est celui-ci — « et RIEN ne déconnecte le réseau déjà ouvert ». 7 constats neufs dans bancs/rendu.js (269 au total), 6 blessures de mutation toutes mordantes, dont « valider DÉCONNECTE le réseau déjà ouvert » (5 échecs) et « /server -m rouvre une fenêtre » — le défaut d'origine du point.
🔨 Ce qui reste avant de le déclarer CONSTATÉ : le voir sur l'application installée. Le banc charge la vraie page dans une vraie fenêtre Electron, ce qui est beaucoup — mais l'installé porte son propre code, et c'est la leçon de la 0.4.3.
58. 🔨 DEUX COLONNES QUE PERSONNE N'ÉCRIT, ET UN BALAYAGE QUI LES VIDE CHAQUE NUIT — mesuré le 31/08/2026
Ce que c'est. IriX_users.is_oper et IriX_users.away ne peuvent jamais valoir oui. Leurs seuls écrivains sont des procédures du point 56 — celles qui ne s'exécutent pas. Et ::soc::scan::daily les remet à non toutes les nuits, avec en commentaire « sera remis à oui via RAW OPER au prochain rejoin » : il désigne nommément le chemin mort comme sa contrepartie.
📌 C'est une forme neuve du motif du 31/08, et la plus retorse rencontrée jusqu'ici. Ailleurs, une procédure morte laissait simplement un trou. Ici un second mécanisme, bien vivant, entretient le trou — et son commentaire explique pourquoi c'est normal. Un lecteur qui vérifie le balayage le trouve juste : il fait exactement ce qu'il annonce. Le mensonge n'est dans aucune des deux moitiés, il est dans leur raccord.
Ce qui a été mesuré, statique puis en base :
| Fait | Comment |
|---|---|
user::set_oper n'a qu'un appelant : on_OPER, injoignable | grep sur tout IriX/ |
user::set_away n'a qu'un appelant : on_AWAY, injoignable | idem |
evt_auth_nick, le second écrivain d'is_oper, a zéro appelant | son propre commentaire le disait déjà |
scan::daily remet les quatre colonnes à zéro chaque nuit | IriX-sql.tcl, UPDATE … SET is_oper='non' puis SET away='non' |
En production : 0 compte à is_oper='oui', 0 oper_type, 0 away='oui' | requête directe sur IriX_users |
Ce que cela empêche, exactement — ni plus, ni moins :
- ⚠
::soc::get_graderendnetadminsur cette seule condition. Ce grade, sommet de l'ordre{user voice operateur admin proprietaire netadmin}, est donc inatteignable par construction. Un opérateur IRC réel redescend surproprietaireouadmin, et son rang de réseau ne compte pas dansirix-chanaccess.tcl. - ⚠
!statsannonce toujours « IRCop : 0 », quel que soit le réseau. - 🟢 Aucune garde n'EXIGE
netadmin— vérifié, etWeb/inc/commandes.phple notait déjà. Rien n'est donc bloqué : le défaut sous-évalue et sous-compte, il n'interdit rien. - 🟢 L'absence est partiellement rattrapée pour
vhost/ident/ippar les numériques 311/320/338/352/354, qui sont atteignables — mais pas pouris_operniaway, qui n'ont aucun repêchage par sondage.
Pour le fermer — ⚠ le portage ne consiste PAS à écrire une branche OPER : ce verbe n'existe pas en S2S. Le signal équivalent est +o sur UMODE2 (branche existante) ou MODE, toutes deux déjà branchées. Il faut donc greffer sur une branche vivante, avec traduction UID→pseudo, et accepter que oper_type reste vide — le S2S ne le transporte pas.
⚠⚠ Et ne pas commencer par le balayage. Retirer la remise à zéro sans le repeuplement figerait is_oper sur des valeurs qui ne redescendraient plus jamais : on remplacerait « toujours faux » par « faux et périmé », ce qui est pire. Les deux moitiés se réparent ensemble ou pas du tout.
🔨 LE CODE EST ÉCRIT — 01/09/2026 — il reste à le CONSTATER en service
Fait exactement comme ce point le prescrivait : greffé sur une branche vivante, oper_type laissé vide, et les deux moitiés du même geste.
::soc::online::refleter_oper (dans irix-online.tcl) reporte le +o d'une connexion dans _users.is_oper. ⚠⚠ Elle vise auth_uid, jamais le pseudonyme — la doctrine du 07/08 : sans cela, reprendre le pseudo d'un opérateur qui vient de partir suffirait à hériter de son grade. Et cela écarte la casse au passage, un UID étant opaque là où SQL et Tcl divergent sur les noms.
Cinq points d'emploi, et il en fallait cinq :
| Quand | Sans lui | |
|---|---|---|
| 1 | un MODE change les umodes | rien ne suit les prises et pertes d'+o |
| 2 | à l'identification | l'ordre normal est +o PUIS s'identifier : au +o il n'y a pas encore d'auth_uid à viser |
| 3 | à la révocation d'auth | !stats compterait des opérateurs partis — ce lecteur-là n'exige pas auth='oui' |
| 4 | au démarrage, remise à zéro | la colonne se figerait sur l'état d'avant : faux et périmé |
| 5 | au burst, repeuplement | après (4), un opérateur qui ne touche à rien resterait non pour toujours |
⚠ Les (4) et (5) sont le défaut symétrique dont ce point prévenait, et ils se gardent ensemble.
📌 away est traitée à part, et par la négative : elle n'a aucun lecteur, son seul écrivain possible relève du même mécanisme mort, et le point 56 a tranché qu'il ne serait pas porté. Le balayage nocturne cesse de la vider — balayer une colonne que nul n'écrit ni ne lit n'était pas faux, c'était du bruit qui avait l'air d'un entretien. ⚠ La colonne n'est PAS supprimée : une migration destructive pour du bruit serait un mauvais échange.
Banc IriX/tests/test-oper-vivant.tcl — 20 constats, 10 blessures toutes mordantes, dont les cinq emplois et le retour du balayage. ⚠⚠ Une onzième blessure a d'abord été annoncée sans mordre, et elle mérite d'être notée : le constat « le balayage n'écrase plus » cherchait un motif où le backtick n'était pas échappé — il rendait donc 0 dans les DEUX mondes. Le banc porte désormais un plancher sur ses propres motifs : on leur donne à lire un échantillon qui doit matcher.
🔨 Ce qui reste, et je ne peux pas le faire seul : IriX relancé, un opérateur qui se met +o, et !stats qui cesse d'annoncer « IRCop : 0 ». Le code neuf n'agit qu'au prochain démarrage.
57. ✅ LA DISTRIBUTION CONNAÎT CINQ PAQUETS — ouvert le 31/08/2026, ÉLARGI le 02–03/09
Constaté en service, pas seulement au banc : un paquet déposé dans Web/.run/ fait apparaître son bloc sur la page et sa clé dans la réponse JSON, /dl/<clé> le sert, et son retrait fait tout redisparaître. Éprouvé dans les deux sens le jour même.
📌 L'inventaire porte désormais CINQ entrées de type client : Windows, les deux macOS (encore inertes, aucun .dmg n'ayant été construit) et les deux Linux, publiés le 02/09. Le manifeste ne liste que ce qui existe, et la page s'allume d'elle-même. La procédure de dépôt — cinq copies — est dans Markdown/WEB.md.
⚠⚠ Ce qui a guidé le chantier : ne pas casser les clients DÉJÀ INSTALLÉS. Un client ≤ 0.6.9 lit fichier/sha256/taille à plat, sans notion de plateforme — les faire varier ferait télécharger un .deb à un Windows, c'est-à-dire casserait le mécanisme même qui sert à mettre les clients à jour. Ces trois champs continuent donc de décrire Windows.
🔴 ET CE N'ÉTAIT PAS FINI, CONTRAIREMENT À CE QUE CE POINT A DIT DU 31/08 AU 03/09. Le site avait sa clé plateformes ; le client ne la lisait pas, et le site n'y mettait pas Linux. Le 02/09, les deux paquets Linux étaient publiés, téléchargeables — et un poste Linux qui demandait une mise à jour recevait quatre-vingt-dix mégaoctets d'installateur .exe, puis un refus. Fermé le 03/09 par la 0.6.10 du client.
⚠⚠ Trois listes recopiées, découvertes le même jour, chacune par une porte différente : la clé plateformes de site_overscript_public(), la liste $systemes de versions-overscript.php, et le libellé du paquet Windows qui ne nommait pas son système sous une colonne « Système ». Chaque fois, les constats existants étaient verts : ils parcouraient ce qui est annoncé, jamais ce qui aurait dû l'être. Les trois dérivent maintenant de distrib_inventaire().
📌 Gardé par test-overscript.php — le compte n'est pas écrit ici, il monte à chaque chantier ; php Web/tests/test-overscript.php le dit.
⚠ Aucune détection du système du visiteur : le site ne lit l'User-Agent nulle part, cet en-tête ment de plus en plus, et deviner mal cacherait à quelqu'un le fichier dont il a besoin. On montre ce qui existe, il choisit.
🟡 Ce qui reste, et ne dépend plus de moi : qu'un .dmg soit construit sur un Mac. Et la décision du compte développeur Apple, sans lequel chaque personne contourne Gatekeeper à la main.
<details><summary>L'état d'avant, gardé pour mémoire</summary>
57 (état d'origine). 🔨 LA DISTRIBUTION NE CONNAÎT QU'UNE SEULE PLATEFORME
Ce que c'est. Une version d'essai d'OverScript part vers un Mac, et le site n'a nulle part où la mettre. Il sert un seul binaire, sous un nom sans plateforme, et latest.yml n'en décrit qu'un.
⚠ Le verrou n'est pas la construction du paquet — elle est prête et éprouvée (dmg + zip, x64 et arm64, bancs/paquet-mac.test.js). C'est la distribution. Même avec un .dmg en main demain, il n'y aurait ni où le déposer, ni comment l'annoncer, ni comment le client saurait lequel prendre.
Ce qu'il faudrait pour le fermer, les fichiers étant déjà repérés :
| Fichier | Ce qui change |
|---|---|
Web/inc/distrib.php, distrib_inventaire() | une entrée par plateforme, aujourd'hui une seule (overscript) |
Web/inc/site.php, site_overscript_public() | rendre un jeu fichier/sha256/taille par plateforme |
Web/site/versions-overscript.php | le libellé « Windows — » est écrit en dur ; proposer selon le système |
Markdown/WEB.md § publication | trois copies manuelles, à décliner par plateforme |
Web/tests/test-overscript.php | les constats de concordance ne regardent qu'un binaire |
⚠⚠ La compatibilité est le point délicat. Le client 0.4.6 attend fichier, sha256 et taille au singulier. Changer leur forme casserait tous les clients installés — c'est-à-dire le mécanisme même qui sert à les mettre à jour. 📌 Il faut donc AJOUTER une section par plateforme en gardant les champs actuels, et non les remplacer.
⚠ Web/.run/overscript-latest.yml est lu par le banc mais absent de l'inventaire : il n'est donc pas servi. À décider en même temps.
🔴 Et une décision qui appartient à SerVuS : sans compte développeur Apple (payant, annuel), chaque personne devra contourner Gatekeeper à la main au premier lancement. C'est écrit dans Dev/OverScript/MACOS.md, et présenté comme attendu — mais cela reste un frein à toute distribution large.
</details>
56. 🔨 UNE TRENTAINE DE PROCÉDURES QUI NE S'EXÉCUTENT JAMAIS — mesuré le 31/08/2026
Ce que c'est. En réparant on_SERVER ce matin, on a trouvé que le défaut n'était pas isolé : c'est un motif, et il touche une trentaine de procédures d'IriX. Deux mécanismes distincts, qui se cumulent.
⚠⚠ Mécanisme 1 — bind raw écoute la connexion CLIENTE
IriX-binds.tcl lie 42 verbes à ::soc::raw::dispatch. Or bind raw d'Eggdrop n'écoute que le flux de la connexion cliente du bot. Une trentaine de ces verbes sont strictement serveur-à-serveur ou client→serveur : un client n'en reçoit jamais.
Les plus coûteuses, parce qu'elles écrivent en base ou ferment des sessions — donc leur mort est silencieuse :
| procédure | ce qu'elle croit faire |
|---|---|
on_SERVER | remplir network/numeric_id/link_by/description — ✅ remplacée le 31/08 par on_burst_serveur |
on_SQUIT | ::soc::srv::offline |
on_SVSKILL, on_KILL | session_close + révocation d'identification |
on_CHGHOST, on_CHGIDENT, on_VHOST | UPDATE …_users SET vhost=/ident= |
on_WEBIRC | UPDATE …_sessions SET ip= |
on_AWAY | seul appelant de ::soc::user::set_away — ⛔ ne sera pas porté, voir plus bas |
📌 LE MOTIF, énoncé une bonne fois — il est revenu CINQ fois
Chacun de ces défauts a la même forme, et aucun n'est une faute de codage :
Le code est juste. C'est son EMPLOI qui manque — et quelque chose donne l'illusion que ça marche.
on_SERVER (colonnes vides), TOPIC (sujet périmé sur le site), track_burst (cinq colonnes vides, rattrapées par reconcile), le 421 (deux journaux au lieu d'un), le client OverScript qui écoutait le mauvais numérique. ⚠⚠ Et à chaque fois un banc existait et restait VERT : il mesurait la chose, jamais son emploi.
📌 D'où la règle qui en sort, et qui vaut pour tout banc neuf : un banc qui éprouve une procédure doit AUSSI éprouver qu'on l'appelle — depuis où, et avec quels arguments. Les deux bancs posés aujourd'hui le font, et leurs blessures de câblage mordent.
⚠ Corollaire, appris le 31/08 : une réparation s'applique à l'instance, jamais au motif. Le piège du burst avait été trouvé et réparé pour les salons des mois plus tôt ; personne n'avait demandé quels autres verbes étaient logés à la même enseigne. Trois l'étaient.
⚠⚠ Mécanisme 2 — le burst est lu par une boucle BLOQUANTE
La lecture du burst se fait dans une boucle qui avale tout avant de poser le fileevent. Tout verbe qui n'arrive que pendant le burst n'atteint donc jamais le dispatcher S2S — même s'il y a une branche. C'est ce qui avait été trouvé pour le burst de salon, réparé pour lui seul.
✅ SID est traité depuis le 31/08 — il cumulait les deux mécanismes (aucune branche et arrivée pendant le burst), et c'est lui qui annonce tous les serveurs derrière le pair.
✅ TOPIC est traité depuis le 31/08 — IriX/tests/test-topic-s2s.tcl, 41 constats, dix blessures de mutation toutes mordantes. C'était le seul des trois à alimenter une vue : le site affiche <bot>_channels.topic (Web/pages/salons.php), et cette colonne n'était écrite que par bind raw TOPIC, donc pour les seuls salons où le bot siège. Ailleurs, le sujet restait faux indéfiniment. ⚠⚠ Les deux formes du verbe ne se distinguent pas par expression régulière — le troisième champ est le poseur au burst et le sujet en service, et « rendez-vous samedi 20 » satisfait le motif long : on compte les paramètres comme IRC les compte, au premier " :", comme le daemon lui-même (parc >= 5).
⛔ AWAY ne sera pas porté, et c'est une décision, pas un oubli. Mesuré le 31/08 : personne ne lit <bot>_users.away ni away_msg — ni le Tcl, ni le PHP, nulle part dans le dépôt. Le serveur tient son propre état et répond lui-même RPL_AWAY (src/cmd/m_query.c). Porter le verbe remplirait deux colonnes que rien n'affiche. ⚠ Si un jour une vue les lit, ce point se rouvre — et c'est alors le même travail que pour TOPIC.
⛔ Les MODE #salon +b du burst non plus. Le daemon les émet bien (s_serv.c, juste avant le TOPIC du burst), et aucun des deux lecteurs ne les voit — c'est exact. Mais ils annoncent des bans préexistants, pas un changement : les enregistrer fabriquerait un événement par ban à chaque montée de lien. Et <bot>_bans n'est pas un reflet du serveur, c'est la liste des bans permanents du bot (newban/delban). Aucune vue n'est donc périmée. ⚠ C'est la différence de nature avec TOPIC, et c'est elle qui tranche — pas la difficulté technique, qui serait la même.
✅ ⚠⚠ DEUX BANCS SONT VERTS SUR DU CODE MORT — LES DEUX SONT REPOINTÉS (02/09/2026)
test-sessions.tcl exécutait on_WEBIRC et constatait que son SQL est correct — un SQL qui ne s'exécuterait jamais. test-auth.tcl gardait la révocation dans on_SVSNICK/on_SVSKILL/on_KILL, révocation que ce chemin ne déclencherait jamais. C'était la confirmation mensongère à l'état pur, et ces deux bancs sont à relire avant de toucher au code.
📌 La couverture a été MESURÉE le 31/08 — et la bonne question n'était pas celle-là
La question utile n'est pas « ces procédures sont-elles mortes ? » (elles le sont) mais « le travail qu'elles prétendent faire est-il fait ailleurs, et INTÉGRALEMENT ? ».
⚠ Cette phrase a annoncé « quatre verdicts » en n'en listant que trois, du 31/08 au 01/09 — et le quatrième manquant était le seul « PAS COUVERT » de tout le point. Un compte écrit à la main au-dessus d'une liste finit toujours par mentir sur elle : on ne les énumère plus. Réponse :
- ✅
on_WEBIRC— COUVERT, et RETIRÉE le 02/09.online::on_uid→session_openécrit déjàip, et c'est la vraie :encode_iplitsockhost, oùadopter_ip_relayeea posé leX-Forwarded-Forvalidé du webchat.online::flushla repose même sur les sessions incomplètes. ⚠ Le SQL mort ne pourrait d'ailleurs rien toucher : il exigesocket_id=0, que plus aucun appelant ne produit. Seul résidu : la ligne d'événementWEBIRC, du journal pur. - ✅
🟡— PORTÉS le 31/08/2026 à 19:10 (on_SVSKILLeton_KILL— PARTIELLEMENT couvertsed5456c), et ce verdict est resté périmé un jour et demi.::soc::ulines::auto::on_kill_s2svit sur le dispatcher S2S ; bancIriX/tests/test-kill-s2s.tcl, 27 constats. L'écart décrit ci-dessous —event_typebloqué à'quit',quit_msgvide, tueur introuvable — est comblé. ⚠⚠ CE VERDICT M'A FAIT REFAIRE LE TRAVAIL le 01/09. J'ai lu « il reste à porter » dans ce registre, et j'ai réécriton_kill_s2sà côté de celui qui existait déjà — plus une brancheKILL - SVSKILLen double qui, en Tcl, aurait masqué la branche existante et supprimé la détection de nos propres pseudo-clients tués (mark_ghost). Défait sans dommage. 📌 Le commited5456cavait porté le verbe et écrit son banc — sans toucher au registre. Un point corrigé dans le code et laissé ouvert ici ne coûte pas rien : il coûte le travail d'un autre jour. C'est la contrepartie exacte de la règle « un point ne sort qu'après avoir été CONSTATÉ fermé » : elle vaut pour la sortie, jamais pour le silence. - ✅ Et la dépendance cachée est désormais GARDÉE DU CÔTÉ QUI ÉMET —
Servers/overtchat-hybrid/tests/test-kill-quit.py, 10 constats, 19ᵉ campagne du lanceur.test-kill-s2s.tclsavait dire que rien ne gardaitFLAGS_KILLED; il ne pouvait pas être ce gardien, étant du mauvais côté du fil. - ✅
on_SQUIT— LE VERDICT QUI MANQUAIT, mesuré le 01/09/2026 : il était PAS COUVERT.<bot>_servers.onlinen'avait que deux écrivains de la valeur'non'—on_SQUIT, morte, et::soc::evt_quit, vivante mais qui ne reconnaît un départ de serveur qu'à un message de QUIT portant deux noms de serveur : la signature d'un netsplit. ⚠⚠ Un délink PROPRE — l'arrêt volontaire d'un service, exactement le scénario Anope — n'en produit aucun. La table gardait donconline='oui'indéfiniment, etSERVERScommeMAPcomptaient un serveur mort comme vivant. 📌 Et le lien VOYAIT ce SQUIT : la branche du dispatcher le lisait, décidait s'il nous visait, et le jetait après unputlog. Porté :::soc::srv::offline_cible, appelée depuis cette branche, du côté « ce n'est pas nous ». ⚠ Elle viseservernameounumeric_id— un SQUIT désigne sa cible par l'un ou l'autre, etsrv::offlinene connaissait que le nom : appelée avec un SID, elle n'aurait rien trouvé et rien dit. BancIriX/tests/test-squit.tclétendu (12 → 20 vérifications), 6 blessures toutes mordantes. - ✅
on_CHGHOST,on_CHGIDENT,on_CHGNAME— PORTÉS le 01/09/2026, et avec eux le point dur décrit plus bas.::soc::ulines::auto::on_chghost_s2svit sur le dispatcher S2S ; ces trois verbes tombaient jusque-là dansdefault, c'est-à-dire dans unputlog (ignoré). ⚠⚠ Mesuré dans le code du serveur : ce sont les SEULS qu'il émette pour un changement d'identité —:<UID-src> CHGHOST|CHGIDENT <UID-cible> <valeur>etCHGNAMEavec un trailing.SETHOST,SETIDENT,SETNAME,VHOST,VIDENTne partent NULLE PART sur le lien ; ils figurent pourtant dans_raw_cmds. Les lister ne coûte rien et n'apporte rien ; croire qu'on les traite coûte cher. ⚠⚠ ET LE CLOAKING (+x) NE PASSERA JAMAIS PAR LÀ : poser+xn'émet aucunCHGHOST, seulMODE <uid> :+xcircule, et chaque serveur recalcule le masque de son côté. IriX ne peut pas apprendre un hôte masqué par le lien — propriété du protocole, à savoir avant d'aller chercher pourquoi une colonne « ne suit pas ». 📌 Un défaut du serveur, trouvé en mesurant :m_oper.c:170émetCHGHOST <username> <hôte>— premier paramètre l'ident, pas la cible, contrairement à ses trois voisins et à son proprems_chghost. Une telle ligne ne désigne aucun UID connu : elle est refusée et dite au journal, jamais appliquée à quelqu'un au hasard. Un constat le garde. BancIriX/tests/test-online-maj.tcl, 34 constats, 15 blessures toutes mordantes. 🟡, mesuré le 01/09/2026. Les sondages par numériques (311/320/352/354) écrivent bienon_CHGHOST,on_CHGIDENT,on_VHOST— PARTIELLEMENT couverts_users.vhost/.identet_sessions.vhost/.ident. ⚠ Trois réserves : la portée se limite aux salons du bot (le WHO horaire boucle sur[channels]sousbotonchan), la latence va jusqu'à une heure, et les lignes_eventsde typeCHGHOST/CHGIDENT/VHOSTsont perdues — ces troisevt_recordn'ont aucun autre appelant. ✅ ET LE POINT DUR QUE PERSONNE N'AVAIT NOMMÉ — RÉPARÉ le 01/09.<bot>_onlinen'avait qu'un seul écrivain devhost,host,identetrealname: la branche d'insertion d'online::flush, alimentée uniquement à l'introduction du client. La branche de mise à jour partielle était câblée sur deux colonnes en dur —foreach col {nick modes}. Un changement d'hôte laissait donc la valeur figée pour toute la durée de la connexion, et c'est la table que le panel montre en direct. ⚠⚠ C'est plus retors qu'une procédure morte : le mécanisme MARCHAIT, sur deux colonnes sur neuf. La ligne existait et paraissait tenue à jour. Une ligne présente et périmée est pire qu'une ligne absente : elle a l'air mesurée. Même piège que les cinq colonnes vides du burst. 📌 Les deux moitiés ont été faites ensemble, et il le fallait : élargir la liste sans écrivain aurait ajouté une capacité morte de plus — le défaut même que ce point recense —, et poser l'écrivain sans élargir la liste n'aurait rien écrit.online::on_hoteest le producteur, la liste passe à{nick modes ident host vhost realname}. ⚠ Passocket_id,ipniis_svc: ils n'ont pas à changer pendant une connexion, et une mise à jour partielle qui les porterait déplacerait une ligne d'un lien à l'autre. ⚠flushn'avait AUCUN banc — les deux qui nommaient::soc::online::la doublaient au lieu de l'exécuter. L'écrivain de la table du direct n'était éprouvé nulle part.- ⛔
on_SVSNICK— NE SE PORTERA PAS, et ce n'est pas un renoncement. Mesuré le 01/09/2026 dans le code du serveur :m_service.c:82faitsendto_one(target, …), passendto_server. Le SVSNICK est ROUTÉ vers le serveur qui héberge la cible, jamais DIFFUSÉ. Un ULine en feuille — c'est notre cas — ne se trouve sur ce chemin que s'il héberge lui-même la cible, ce qui n'arrive pas. Aucune branche de dispatcher ne le recevrait jamais. Ce qui circule ensuite est unNICKordinaire, émis par le serveur du client : à le lire, un renommage imposé est indiscernable d'un changement de pseudo volontaire. ✅ La trace s'écrit donc à l'ÉMISSION, dansnickguard::enforce— le seul instant où l'information « ce renommage a été imposé, et par nous » existe quelque part.evt_record SVSNICKy est posé après l'émission (une trace écrite avant décrirait ce qui n'a pas eu lieu si le lien tombait entre les deux). ⚠ Le journal du service ne remplace pas l'événement :Web/pages/activite.phplit_events, passvc_log— deux destinations, deux publics. Banctest-nickguard-exempt.tclétendu (12 → 18 constats), 5 blessures toutes mordantes. 📌 Un verbe qu'on ne peut pas recevoir n'a pas de « chemin vivant » à trouver : la question cesse d'être « où le brancher » et devient « où l'information naît-elle ».
⚠⚠ ET LA DÉPENDANCE CACHÉE (gardée depuis le 01/09 — voir plus bas) : toute cette couverture tient à ce que overtchat-hybrid émette KILL et QUIT pour la même victime. Il pose bien FLAGS_KILLED (m_oper.c, m_service.c) mais ne le lit nulle part — pas de IsKilled dans l'arbre — là où le hybrid d'origine s'en sert pour supprimer le QUIT. 📌 Si quelqu'un « corrigeait » cet oubli, les sessions des tués resteraient ouvertes indéfiniment et l'auth survivrait au kill partout hors des salons du bot. C'est le premier banc à écrire ici — avant toute suppression de code mort.
✅ CE BANC EST ÉCRIT — 01/09/2026, et l'étape 0 est franchie
Servers/overtchat-hybrid/tests/test-kill-quit.py, 10 constats, inscrit au lanceur (19ᵉ campagne). Le comportement redouté est mesuré, pas supposé : un KILL produit bien un KILL et un QUIT sur le lien, et le QUIT porte « Killed ».
⚠⚠ Le point d'observation est un pair S2S BRUT, et il devait l'être. Un témoin client n'aurait rien prouvé : un serveur voisin qui reçoit un KILL fait sortir la victime de ses salons et émet un QUIT local à ses membres. Un client aurait donc vu un QUIT même si aucun n'avait traversé le lien — le banc serait passé par une autre porte que celle qu'il prétend garder.
Mutation : le « correctif » lui-même — lire FLAGS_KILLED dans exit_client_from pour supprimer le QUIT redondant, recompiler, relancer. Le banc rougit. ⚠ La restauration recompile aussi : laisser le binaire blessé ferait mentir les dix-huit autres campagnes, en vert et en silence.
⚠⚠ ET LA MUTATION A TROUVÉ QUE LE BANC MENTAIT. Au premier essai, le constat capital — « ET LE QUIT AUSSI » — est resté VERT sur un serveur qui supprimait le QUIT : le filtre cherchait \bQUIT\b n'importe où dans la ligne, et la raison du kill que j'avais écrite était « essai de la dependance KILL/QUIT ». La ligne de KILL se comptait donc comme sa propre preuve. Un autre constat a rougi à sa place, ce qui aurait envoyé chercher au mauvais endroit. 📌 Savoir QUEL constat rougit importe autant que de le voir rougir. Le banc lit désormais le verbe en tête de ligne, et porte un plancher qui lui redonne les deux lignes qui l'avaient trompé.
✅ LE PORTAGE (1) EST FINI — 01/09/2026. Tous les verbes dont le travail comptait ont trouvé leur chemin vivant, ou ont reçu un verdict écrit :
| Verbe | Où il vit maintenant |
|---|---|
SERVER, SID | on_burst_serveur — 31/08 |
TOPIC | on_topic_s2s — 31/08 |
KILL, SVSKILL | on_kill_s2s — 31/08 |
SQUIT | srv::offline_cible — 01/09 |
CHGHOST, CHGIDENT, CHGNAME | on_chghost_s2s — 01/09 |
SVSNICK | ⛔ inrecevable ; trace écrite à l'émission, dans nickguard::enforce |
AWAY | ⛔ décision du 31/08 : personne ne lit ces colonnes |
WEBIRC | ✅ déjà couvert par online::on_uid |
SETHOST, SETIDENT, SETNAME, VHOST, VIDENT | ⛔ le serveur ne les émet nulle part |
⚠ Les raisons de test-verbes-atteignables.tcl ont été mises en accord avec le code le 01/09 : elles décrivaient l'état d'avant les portages, et une raison périmée dans une table d'exceptions est exactement ce qui a fait refaire un portage déjà fait ce jour-là.
✅ L'ÉTAPE (2) EST FAITE — 02/09/2026
Huit procédures et douze verbes sont partis ENSEMBLE — on_KILL, on_SVSKILL, on_SVSNICK, on_CHGHOST, on_CHGIDENT, on_CHGNAME, on_VHOST, on_SQUIT, plus leurs branches du switch de ::soc::raw::dispatch et leurs entrées de _raw_cmds. IriX-sql.tcl perd 133 lignes.
⚠ Aucun compte n'est écrit ici. La première version de ce paragraphe annonçait « 69 mots à 57, et 32 à 20 » — justes à la minute où ils ont été tapés, faux au commit SUIVANT, qui retirait on_WEBIRC. Le banc d'inventaire affiche les deux nombres à chaque passage : tclsh IriX/tests/test-verbes-atteignables.tcl.
⚠ Le préalable a été traité EN PREMIER, et c'était le bon ordre. test-auth.tcl exigeait que on_SVSNICK, on_SVSKILL et on_KILL — mortes — appellent evt_deauth_nick : un constat vert sur du code mort. Il vise désormais on_kill_s2s, et trois constats neufs exigent que les trois noms aient quitté le fichier — la suppression et sa garde posées dans le même geste. ⛔ SVSNICK a quitté cette liste avec sa raison : un renommage ne coupe pas la connexion.
⚠⚠ ET LA MUTATION A TROUVÉ QUE LE CONSTAT REPOINTÉ NE GARDAIT RIEN. Il cherchait la chaîne evt_deauth_nick dans le corps de la procédure — donc il était satisfait par un commentaire qui la nomme, et par la garde if {[llength [info commands ::soc::evt_deauth_nick]]}, qui teste l'existence sans jamais appeler. Retirer la seule ligne d'appel de on_kill_s2s laissait le banc entièrement vert. Le motif exige maintenant le nom suivi d'un argument, commentaires retirés, avec deux planchers qui lui donnent à lire l'appel et la garde. 📌 Chercher un texte prouve qu'on l'a écrit, jamais qu'il AGIT — et ce banc-là gardait la révocation d'identification.
✅ on_WEBIRC est partie elle aussi — même journée, même ordre. Elle était doublement morte, et c'est ce qui la rendait exemplaire :
WEBIRCest envoyé par une passerelle AU SERVEUR à l'inscription. La connexion cliente du bot ne le reçoit jamais.- ⚠⚠ Et son SQL visait
WHERE … socket_id=0. Mesuré : le seul appelant vivant desession_openlui passe$id, l'identifiant du lien — jamais 0. Même exécutée, la requête n'aurait touché aucune ligne.
⚠ test-sessions.tcl l'appelait en trois endroits et constatait « écrit bien dans les sessions » — sur un db_exec doublé. Un banc qui appelle lui-même une procédure que rien n'appelle mesure sa propre main. Les trois appels sont retirés ; un constat exige désormais son absence, avec un plancher qui prouve que le source a bien été lu.
📌 Rien n'est perdu : online::on_uid ouvre déjà la session avec l'adresse, et le filet « aucune écriture de session sans lien » la gardait déjà. On retire une promesse, pas un service.
⚠⚠ CETTE LISTE EN OUBLIAIT TROIS — remesuré le 04/09/2026. Elle annonçait « les douze procédures mortes restantes » ; il y en a quinze. Les trois absentes sont on_OPER, on_AWAY et on_SERVER — et ce sont précisément les seules dont le travail a déjà trouvé une place, donc les seules immédiatement retirables selon la règle du point.
| Encore là, sans verdict (11) | Portées ou tranchées (4) |
|---|---|
on_SVSJOIN · on_SVSPART · on_SWHOIS · on_NETINFO · on_PROTOCTL · on_STARTTLS · on_REHASH · on_RESTART · on_WATCH · on_SILENCE · on_DCCALLOW | on_SVSMODE → branche S2S vivante · on_OPER → refleter_oper (point 58) · on_SERVER → on_burst_serveur (31/08) · on_AWAY → verdict ⛔ écrit |
⚠ Deux pièges avant de retirer quoi que ce soit. ::soc::user::set_oper et ::soc::user::set_away n'ont qu'un seul appelant chacune — on_OPER et on_AWAY : les retirer sans elles laisse deux procédures orphelines. Et SAJOIN/SAPART réutilisent on_SVSJOIN/on_SVSPART : retirer une de ces procédures impose de retirer deux verbes.
📌 Le frein n'est pas un banc — aucun n'exécute ni n'exige l'une des quinze. C'est le triplet : la procédure, sa branche du switch, son verbe dans _raw_cmds, et son entrée dans INATTEIGNABLES. En retirer trois sur quatre fait rougir test-verbes-atteignables.tcl, ce qui est exactement ce qu'on lui demande.
✅✅ L'ÉTAPE (2) EST TERMINÉE — les QUINZE sont retirées, 04/09/2026
Les onze dernières ont reçu leur verdict à la lecture, une par une, et c'est le même : leur travail est soit vide, soit une trace, soit déjà fait par une procédure vivante.
| Retirée | Pourquoi il n'y avait rien à porter |
|---|---|
on_SVSJOIN · on_SVSPART | l'effet revient en JOIN/PART, que le bot enregistre déjà |
on_SWHOIS | on_320 écrit le vhost en base — et c'est un numérique, donc reçu ; le dispatcher S2S a de plus sa propre branche SWHOIS |
on_NETINFO | on_005 pose network_name, et 005 arrive à chaque connexion |
on_PROTOCTL | une trace ; le burst S2S lit déjà PROTOCTL |
on_STARTTLS · on_REHASH · on_RESTART | une ligne de journal sur un verbe jamais reçu |
on_WATCH · on_SILENCE · on_DCCALLOW | corps vide |
📌 Le cas le plus instructif est on_NETINFO : elle posait ::soc::network_name, une variable que trois endroits lisent. Si elle en avait été le seul écrivain, la retirer aurait cassé le nom du réseau. Elle ne l'était pas — on_005 fait le même travail sur un chemin vivant. La question n'est jamais « cette procédure fait-elle quelque chose », c'est « quelqu'un d'autre le fait-il déjà ».
Après retrait : _raw_cmds passe de 52 à 39 verbes liés, et INATTEIGNABLES de 15 à deux — GLOBOPS et LOCOPS, qui partagent on_GLOBOPS, vivante parce que WALLOPS l'atteint.
⚠⚠ Et le geste a failli couper le bot, DEUX FOIS
🔴 Une trace insérée au milieu d'un commentaire. Le script mémorisait l'indice de la première procédure retirée, puis coupait dix autres morceaux avant de s'en servir : l'indice avait bougé, la trace s'est glissée dans un commentaire existant, et la fin de celui-ci s'est retrouvée sans # — donc exécutée. invalid command name "::soc::online::refleter_oper"`.
📌 C'est test-chargement.tcl — le point 73, écrit le matin même — qui l'a attrapé, et lui seul : le défaut du 26/08 reproduit à l'identique, une procédure inexistante appelée au chargement. Les autres bancs extraient des procédures ; aucun n'exécute le corps du script.
⚠ Un nettoyage d'espaces sur tout un fichier. Pour retirer treize mots, la première version a réécrit 152 lignes sur 300 de IriX-binds.tcl — toute l'indentation écrasée, diff ingérable. Un nettoyage se borne à la zone qu'on modifie, sinon il fabrique un dégât plus grand que celui qu'il corrige.
✅ LES QUATRE ÉLIGIBLES SONT RETIRÉES — 04/09/2026
on_SVSMODE, on_OPER, on_SERVER et on_AWAY sont parties avec les quatre côtés de leur triplet chacune. Il reste onze procédures mortes, celles qui n'ont toujours ni portage ni verdict.
Sont parties avec elles :
::soc::user::set_operet::soc::user::set_away— elles n'avaient qu'un appelant chacune, précisémenton_OPEReton_AWAY. Les laisser aurait fait deux procédures orphelines à la place de deux mortes ;- le réglage
track_away, qui n'avait plus aucun lecteur. ⚠ Un réglage proposé à l'exploitant et qui ne commande rien est un mensonge d'interface.track_operettrack_chghostrestent : le premier est lu paron_GLOBOPS(vivante par WALLOPS), le second paron_chghost_s2ssur le chemin S2S.
⚠⚠ ET J'AI FAILLI REFAIRE LE PIÈGE DU switch. Le premier jet laissait à la place des quatre branches un commentaire expliquant leur départ. En Tcl, un # dans un switch n'est pas un commentaire : c'est un MOTIF — il devient une paire avec le mot suivant et décale tout le reste. C'est le défaut qui avait coupé le serveur à 89 secondes. Attrapé avant la première mesure, parce que la règle était écrite ; les explications sont donc au-dessus des procédures, jamais dans le switch.
Le garde-fou : test-verbes-atteignables.tcl, 14 constats, 5 blessures de mutation sur 5 vues — et ce qu'elles éprouvent n'est pas « les quatre sont parties », c'est que le banc refuse un triplet incomplet : un verbe relié sans branche, une branche orpheline, un nom fantôme resté dans INATTEIGNABLES, une raison réduite à un mot posé pour le faire taire.
📌 Campagne complète après retrait : 148 bancs, 1 échec, le même qu'avant — celui du binaire du serveur, non construit sur le clone Windows. ⚠ Trois commentaires de bancs décrivaient ces procédures au présent : remis au passé, avec la date du retrait. Un commentaire juste hier est un mensonge aujourd'hui, et rien ne le lit. 📌 Piège relevé au passage : on_GLOBOPS est VIVANTE. Deux de ses trois verbes sont inatteignables, mais le troisième — WALLOPS — est atteignable. Compter les verbes morts d'une procédure ne dit pas qu'elle est morte.
⚠ Contradiction interne corrigée en passant : l'étape 3 ci-dessous réclamait un banc d'inventaire déclaré POSÉ quinze lignes plus haut (test-verbes-atteignables.tcl). C'est l'étape 0 — ce banc-ci — qui manquait, pas la 3.
Les résidus mesurés au passage
- ✅
on_006retiré le 31/08 — une branche duswitchappelant une procédure vide, pour un numérique absent de_raw_cmdsdonc jamais reçu. Deux mensonges superposés, sans décision à prendre : le corps ne faisait rien. - ✅
421: le doublon est TRANCHÉ le 31/08.raw::on_421a été retiré. ⚠⚠ La raison qui faisait garder les deux — « retirer l'un OU l'autre perd quelque chose : le nom de la commande d'un côté, l'entrée d'événement de l'autre » — était fausse. Elle avait été raisonnée, jamais mesurée.on_numericenregistre le texte entier du numérique, «<botnick> <commande> :Unknown command», nom compris. Il suffisait de l'exécuter une fois pour le voir : c'est désormais un constat du banc, gardé pour que la perte se voie si quelqu'un allègeon_numeric. 📌 Une exception nommée coûte DEUX vérifications, pas une : qu'elle soit admise, et que sa raison soit encore vraie. Celle-ci a tenu des semaines. - ✅
track_burst: REBRANCHÉE le 31/08, pas supprimée. Elle était bien inatteignable — son seul appel, danson_read, exige un état que lefileeventne connaît jamais. ⚠⚠ Mais la supprimer aurait été une faute : la boucle bloquante refaisait une partie seulement de son travail. Elle posait le pseudo (track_user) et jamaisonline::on_uid, qui renseigneident,host,vhost,ipetrealnamedans<bot>_online. Les personnes déjà connectées au montage du lien arrivaient donc au panel avec cinq colonnes vides — etNICKcommeQUITdu burst étaient perdus, si bien qu'un partant restait « en ligne » pour toujours. ⚠⚠ Ce qui rendait ce défaut invisible :online::reconcileles rattrape au scan suivant et les inscrit « avec ce qu'on a ». La ligne existait, elle avait l'air mesurée. Une ligne présente et creuse est pire qu'une ligne absente. Banc :IriX/tests/test-burst-clients.tcl, 22 constats, sept blessures toutes mordantes, dont l'ordreLINKED→fileevent.
- 🟡
extraireest fragile, et la copie est dans TRENTE bancs — pas six. (Chiffre remesuré le 04/09/2026 : trente copies, dont vingt-trois encore en version positionnelle. « Six » datait du jour où la question s'est posée, et n'a jamais été recompté.) Cette procédure — celle qui sort une proc du fichier sans sourcer les 13 000 lignes — cherche les accolades par position : premier{, premier}, puis premier{. Cela ne tient que si la signature n'a qu'un seul argument par défaut.track_bursten a deux ({label ""} {en_burst 0}) et l'extraction part du milieu de la signature. ⚠ Le symptôme est bruyant — « missing close-brace » —, donc ce n'est pas un mensonge silencieux ; mais la version appariée existe désormais danstest-burst-clients.tclet les autres copies restent à aligner.
⚠⚠ ET CE N'EST PAS UN « ALIGNEMENT » — mesuré le 04/09/2026. Le mot suppose des copies identiques à remplacer d'un geste. Les vingt-quatre versions positionnelles portent en fait quinze empreintes distinctes : chaque banc a adapté la sienne. Aucune ne partage lire_source ni apparier — chacune rouvre le fichier elle-même.
📌 Le remède n'est donc pas de recopier la bonne version vingt-quatre fois — ce serait aggraver ce qu'on corrige. C'est un module auxiliaire (IriX/tests/_extraire.tcl, sur le modèle de _edition-lire.tcl, que le glob test-*.tcl de bancs.sh ne ramasse pas), et chaque banc qui le source.
⚠ Le chantier demande de lancer chaque banc AVANT et APRÈS et de comparer sa ligne de résumé : quinze variantes veut dire quinze comportements possiblement différents, et un banc qui passerait de « 22 constats » à « 21 » sans qu'on le voie serait pire que la fragilité qu'on corrige. Non entrepris pour cette raison — pas par manque de temps, mais parce que le geste est plus gros que ce que ce point annonçait.
📌 Le symptôme, lui, reste bruyant (« missing close-brace ») : ce n'est pas un mensonge silencieux, et rien ne presse.
✅ Le banc d'inventaire est POSÉ — 31/08/2026
IriX/tests/test-verbes-atteignables.tcl, 9 constats, six blessures de mutation toutes mordantes. Il n'exige pas qu'on supprime les trente procédures mortes ; il exige qu'aucun verbe ne soit lié sans avoir été classé — atteignable, ou inatteignable avec sa raison écrite.
⚠ Il n'empêche pas les trente qui existent. Il empêche le trente-et-unième. La question « un client reçoit-il ce verbe ? » est exactement celle que personne ne s'était posée.
📌 Il dérive la liste du fichier au lieu de la recopier, refuse une raison de moins de 25 caractères, refuse qu'un nom survive dans la liste après avoir quitté les binds, et croise les branches du switch avec ce qui est réellement lié — c'est ce dernier constat qui a trouvé on_006.
⛔ Pour fermer — et pourquoi ce n'est PAS « supprimer le code mort »
⚠⚠ Retirer ces procédures AVANT d'avoir porté leur travail transformerait un code mort en fonctionnalité DÉFINITIVEMENT ABSENTE. Le trou existe déjà ; l'effacer le rendrait invisible.
✅ C'est pourquoi le portage a précédé la suppression, et non l'inverse. Le 02/09, huit procédures sont parties AVEC leurs douze verbes — on_KILL, on_SVSKILL, on_SVSNICK, on_CHGHOST, on_CHGIDENT, on_CHGNAME, on_VHOST, on_SQUIT — chacune après que son travail eut trouvé un chemin vivant ou reçu un verdict écrit. ✅ on_WEBIRC a suivi le même jour, une fois test-sessions.tcl repointé — voir plus bas.
L'ordre qui tient :
- Porter le travail utile vers
::soc::ulines::auto::dispatch, un verbe à la fois, avec son banc — comme cela a été fait pourSERVERpuisSID. - Puis seulement retirer la procédure morte et son verbe de
_raw_cmds— les deux ensemble : laisser la proc sans bind reproduit exactement le mensonge. - Un banc d'inventaire qui exige que chaque verbe de
_raw_cmdssoit soit connu-atteignable, soit nommé avec sa raison — le motif des exclusions nommées déjà employé ailleurs. C'est lui qui empêchera d'en rajouter un trentième sans s'en apercevoir.
⚠ Ne pas commencer par (2). Et ⚠ test-branche-morte.tcl ne peut pas attraper ceci : son en-tête le dit lui-même — « il ne lit pas si la procédure est appelée ».
55. 🔨 Le panneau du salon par GRADE — LE CODE EST ÉCRIT, il reste à le CONSTATER — DEMANDÉ par SerVuS le 28/08/2026
Ce qu'il demande : dans OverScript, que les cases du panneau de gestion se débloquent selon le grade — un IRCop agit sans être @, seuls les @ interviennent, seul l'Owner pose +i, et +O est réservé aux IRCop.
⚠⚠ DEUX DE CES RÈGLES N'EXISTAIENT PAS — mesuré dans src/cmd/m_channel.c, et corrigé le 28/08/2026 :
| Mode | Demandé | Ce que le serveur exigeait | Ce qu'il exige depuis |
|---|---|---|---|
+i | Owner | CHFL_HALFOPS — un halfop suffisait | CHFL_CHANOPS — voir l'encadré |
+O | IRCop | CHFL_CHANOPS — un chanop suffisait | être opérateur IRC |
IRCop sans @ | doit pouvoir | ✅ il pouvait déjà | inchangé, et l'acte est signalé |
⚠⚠ « SEUL L'OWNER POSE
+i» A ÉTÉ ÉCRIT, ESSAYÉ, ET ANNULÉ LE JOUR MÊME. Le fondateur d'un salon reçoitCHFL_CHANOPet rien de plus —+qest réservé aux services, donc aux seuls salons enregistrés. Exiger l'Owner rendait+iimpossible sur tout salon ordinaire : plus personne n'aurait pu fermer sa porte, sauf un IRCop passant outre. C'est la campagnetest-commandesqui l'a dit, par cinq constats rouges décrivant un usage parfaitement légitime — ouvrir un salon privé et le fermer. Tranché par SerVuS le jour même : le chanop. 📌 Un mode qu'on ne peut pas poser n'est pas une règle sévère, c'est un mode mort.
🔴 Les deux décisions, prises le 28/08/2026 — et TENUES
- On a changé le SERVEUR, et le client suit. Un grisage côté client seul se contourne avec n'importe quel autre client IRC : la règle n'existerait que pour qui utilise le nôtre.
- Le serveur ANNONCE le barème en
005, et le client le lit — jetonCHANMODEREQ, dérivé de la tablechanmodes[], jamais écrit à la main.
✅ Ce qui est ÉCRIT, et vert au banc
Le serveur (Servers/overtchat-hybrid/, version 4.29.0) :
+i→CHFL_CHANOPS.+O→ champ neufoper_only, testé avanthave & needs. ⚠ L'ordre porte tout le sens : placé après, un chanop serait passé par la branche du statut. ⚠needsne pouvait pas l'encoder — il ne parle que de statuts de salon, et0y signifie déjà « serveurs seuls ».- ⚠ La garde vaut dans les DEUX SENS : le propriétaire du salon ne peut pas davantage retirer
+Oque le poser. Une garde qui ne tiendrait qu'à la pose laisserait n'importe qui rouvrir un salon réservé, ce qui revient au même. chanmode_req_token()dérive le jeton. Forme réelle, lue sur une vraie connexion :q:qa,o:klLjJntispRMCNSTzQVKcDuoh,h:beImv,*:O,!:rPdA. ⚠ 63 octets, et le tampon des jetons deisupport_build()en tenait 64 — unsnprintfqui déborde tronque en silence, et le client aurait lu un barème amputé de ses derniers modes. Porté à 96.- Documenté dans
Servers/overtchat-hybrid/PROTOCOL.md, §8.
Le client (dépôt séparé Dev\OverScript) :
isupport.jslitCHANMODEREQ. ⚠ Absent ⇒ comportement d'avant, sans quoi OverScript cesserait de marcher sur les autres réseaux — aucun ne l'envoie.- ⚠⚠
session.jssait enfin si l'on est opérateur IRC. Il ne le savait pas : il connaissait tout des grades de salon et rien du grade réseau. Deux sources, le381et unMODEsur soi-même — le381seul aurait raté quiconque est déjà opérateur en arrivant. - ⚠ Le barème a dû être nommé dans
Session.vue():regles.brutgarde bien les jetons inconnus, mais il ne traverse pas l'IPC. modes.jsporte la comparaison (autorise),app.jsgrise — et dit pourquoi, en titre de ligne : un champ éteint sans motif se lit comme une panne du client. ⚠ Aucun barème n'est écrit dans le client.
Les bancs : tests/test-modes-grade.py, 18ᵉ campagne, inscrite au lanceur le jour même — 103 constats. ⚠⚠ Elle ne porte aucune liste de modes : elle lit le jeton, puis éprouve chacune de ses affirmations contre le serveur, au grade du dessous puis au grade annoncé. C'est le seul montage où le jeton ne peut pas mentir. Cinq mutations attrapées, chacune par le constat qui la concerne. Côté client : 498 au npm test, 143 au npm run rendu.
⚠⚠ Ce que ce chantier a appris — quatre fois « un banc ne mesurait pas ce qu'il croyait »
- Une régression introduite en écrivant le grisage :
autorise()rendaittruedès que le serveur se taisait, si bien qu'un visiteur sans aucun grade voyait tous les champs actifs — l'inverse exact de la veille. C'est un constat debancs/rendu.jsécrit bien avant ce chantier qui l'a dit. La garde de base est désormais explicite, et une blessure la garde. - Les acteurs muets mouraient en cours de campagne (
pingfreqdépassé), et le mode alors examiné paraissait coupable d'un dégât qui ne venait que de la durée de la campagne. - Le succès était mesuré sur l'ÉCHO, pas sur l'ÉTAT :
+Orougissait alors que le serveur l'avait bien posé — l'avis d'outrepassement était arrivé le premier et la fenêtre de lecture s'était refermée dessus. On lit maintenant le324, etNAMESpour les grades, qui n'y figurent jamais. - ⚠⚠ Un serveur d'essai d'une mutation précédente avait survécu et tenait le port. Les deux campagnes suivantes ont mesuré son binaire, en annonçant 31 constats rouges qui ne disaient rien de leur propre blessure — et ces chiffres ont failli être écrits ici comme une preuve. La campagne refuse maintenant de démarrer si le port est pris. 📌 Parler au mauvais serveur ne se distingue en rien de marcher.
- ⚠⚠ Et le quatrième a été DIAGNOSTIQUÉ DEUX FOIS À CÔTÉ. La campagne passait lancée seule et rougissait en dix-huitième position, sur les cinq modes de grade. J'ai accusé un délai de lecture, puis un tampon mal vidé ; le vert obtenu entre-temps n'était que de la chance. La cause est le « : » du paramètre final — le
353s'écrit… #salon :~Chef @Chanop, le premier nom porte le deux-points, et l'extraction le ratait : donc uniquement quand le pseudo cherché était en tête, au hasard de l'ordre des présents. 📌 Une intermittence n'est presque jamais une question de vitesse — c'est un cas particulier qu'on ne rencontre qu'une fois sur deux. Chercher d'abord ce qui VARIE d'une exécution à l'autre.
Trouvés au passage — CORRIGÉS AU DÉPÔT le 30/08/2026, pas encore en service
⚠⚠ Ces correctifs vivent dans le serveur C : ils n'atteindront la production qu'à la recompilation, celle-là même qu'attend le constat du point 55. Ils partiront donc avec lui, dans le même geste — ce qui se décide avant, pas après.
📌 La liste de ce que la recompilation emportera (à relire avant de la lancer) : le barème
CHANMODEREQdu point 55 ·OPRIV_OVERRIDEdésormais lu, avec le resserrage deClaudeMod· le doublonINVEXretiré · les cinq actes en force désormais annoncés aux opérateurs (4.31.0), et l'annonce qui ne part plus quand+jou+Mrefusent l'acte ensuite ·OPRIV_SEE_SECRETdésormais lu (4.32.0), qui retire au seul grademoderateurla vue des salons cachés et de la clé+k·OPRIV_SEE_REALHOSTlu (4.35.0), aux trois sitesWHOIS/USERHOST/TRACE— il restreintircopetmoderateur. ⚠ Leifde WHOIS a dû être scindé (il emportait aussi les modes de la cible), et TRACE serait devenu le contournement : son commentaire assumait d'afficher l'hôte réel « puisqu'on l'obtient déjà par WHOIS » — vrai jusqu'au jour où WHOIS se resserre.⛔ CE QUI N'EST PAS DANS CETTE LISTE, ET NE DOIT PAS Y ENTRER :
OPRIV_SVSCMDS. Le brancher serait une panne totale des services — IriX et Anope passent en clients U-linés, sansUMODE_OPERni bloc oper. Le raisonnement mesuré vit dansServers/overtchat-hybrid/include/conf.h, à côté du#define. ⚠chghostetadminsont des bits menteurs (ils gardent une commande qui n'existe pas, ou rien du tout) : leur sort est une décision, pas un correctif.see-realhostse branchera, mais pas d'un seul&&.
- ✅
OPRIV_OVERRIDEn'était lu nulle part. La constante existait (include/conf.h), le mot-cléoverrideétait accepté dans les blocs oper (src/conf.c), documenté dansovertchat.conf.exampleet distribué par grade parTools/opers-sync.sh— et rien dans le serveur ne le consultait. Tout IRCop passait outre, que le privilège lui ait été accordé ou non ; le retirer d'un bloc oper n'avait aucun effet. Un droit qui paraît réglable et ne l'est pas est pire qu'un droit absent : on croit l'avoir borné. La mesure a montré le trou plus large que le diagnostic initial — il ne s'agissait pas demode_permitted()seul, mais de huit endroits :oper_passe_outre()desrc/channel.c(JOIN et PRIVMSG, donc+b,+i,+k,+l,+m,+Jet le mute), plus MODE, KICK ×3, TOPIC et INVITE danssrc/cmd/m_channel.c. ⚠⚠ Ce que la correction change pour de vrai, et qu'il faut savoir avant de recompiler : les grades queopers-sync.shnommeglobal,ircopetmoderateurne demandent pasoverride— ils passaient outre quand même, désormais non. C'est exactement ce que ces grades DISENT depuis toujours ; le serveur ne les lisait pas.ClaudeModest dans ce cas ;ClaudeOps,SerVuS,netadmin(« all ») etadminsont inchangés. 📌 Avant de durcir un droit, mesurer QUI le possède réellement — la même leçon que+ideux jours plus tôt, et cette fois la mesure a montré que le panel voulait déjà ce resserrage. ✅ TRANCHÉ le 30/08/2026 : on GARDE le resserrage,overriden'est PAS ajouté au grademoderateur. Les trois raisons, dans l'ordre où elles ont pesé : (a) le coût est asymétrique —opers-sync.shrégénèreopers.confdepuis la base et recharge par SIGHUP, donc la décision s'annule à chaud, sans recompiler ni couper le réseau, tandis qu'élargir « en attendant » rendrait le privilège décoratif une seconde fois ; (b) l'intention était déjà écrite dans le grade (« volontairement étroit : de quoi calmer un salon, rien pour couper le réseau ni voir ce qui est caché ») — le serveur ne fait que commencer à lui obéir ; (c) la doctrine tient : un modérateur gardekilletkline, il agit sur les gens dans ce qui est visible, et n'entre pas dans les espaces fermés — cohérent avec le fait qu'il n'a passee-secretnon plus. ⚠ Conséquence assumée :ClaudeMod, le compte de vérification, échouera désormais sur tout contrôle exigeant d'entrer dans un salon fermé. C'est voulu — il faudra passer parClaudeOps, ce qui rend l'acte visible au lieu de le noyer dans un compte de service. ⚠ Restent hors du correctif, volontairement :OPRIV_SEE_SECRET, lui aussi jamais lu (visibilité des salons+s/+pdans NAMES, LIST et la clé+k) — autre droit, autre décision ; et les gardesbadchansetMAXCHANNELSPERUSER, qui ne sont pas des modes de salon. Quatre autres privilèges ne sont lus nulle part non plus :CHGHOST,SEE_REALHOST,ADMIN,SVSCMDS. - ✅
INVEXétait annoncé deux fois dans le005(src/ircd.c) : la forme nue en plus d'INVEX=I. La nue est retirée — un client qui range les jetons dans un sac gardait le dernier vu, donc celui sans valeur, et en déduisait la lettre au lieu de la lire. ⚠ Il avait survécu parce qu'aucun banc ne lisait le005dans son ENSEMBLE, seulement trois jetons interrogés par leur nom : le constat neuf deharness.pyrefuse désormais tout doublon.
| Pour fermer | 🔨 IL RESTE LE CONSTAT, et lui seul. Le code est écrit et vert au banc ; écrire du code prouve qu'il existe, jamais qu'on peut s'en servir. Il faut (1) recompiler et remettre le serveur en service — procédure entière en section 6 de Servers/overtchat-hybrid/DEPLOIEMENT.md — puis (2) sur le vrai réseau, dans OverScript : un halfop refusé sur +i, un chanop refusé sur +O, un IRCop qui passe et dont l'acte est signalé, et le panneau qui grise en lisant le jeton. ⚠ Ce geste demande la présence de SerVuS : la remise en service coupe le réseau. |
|---|
47. ✅ Le chat ouvrait un onglet de plus à chaque sortie, et le suivant redemandait le pseudo — CONSTATÉ FERMÉ le 25/08/2026, au navigateur
Signalé par SerVuS le 23/08/2026, en testant le site :
« quand on va sur une autre page … ça ouvre bien un nouvel onglet mais celui-ci si on va sur la page chat ça demande pseudo etc… (à nouveau malgré 1 onglet actif avec le chat de connecté) et on ouvre X onglets à chaque fois »
Deux symptômes, UNE SEULE racine — Web/inc/site.php, dans site_liens_en_onglet() :
$ajout = ' target="_blank"';
if (!preg_match('/\brel\s*=/i', $attrs)) { $ajout .= ' rel="noopener"'; }
- (A) les onglets s'accumulent :
_blankest un nom anonyme. Il ne désigne aucun contexte réutilisable, donc chaque clic crée un onglet neuf. Aucun nom de fenêtre n'est posé sur les sorties — le seul nom du projet,site_chat_onglet(), est réservé à l'onglet du chat. - (B) le pseudo est redemandé : c'est le même code. Un contexte ouvert avec
noopenervit dans un groupe de contextes isolé, et un nom de fenêtre ne se résout que dans son propre groupe. Le script de retour faitwindow.open('', NOM)pour rebasculer sur le chat, obtient un onglet blanc, le referme, et laisse le lien naviguer — la page chat se recharge à neuf, donc panneau pseudo.
⚠ Rien n'est persisté, et c'est assumé : ni pseudo, ni mot de passe, ni socket. Le seul localStorage est le verrou overtchat:chat-actif, qui ne porte qu'un signe de vie — et il n'est consulté qu'au submit, jamais au chargement. D'où l'ordre absurde vécu par le visiteur : on lui demande son pseudo, puis on lui dit que le chat est déjà ouvert ailleurs.
⚠⚠ CE N'EST PAS UN OUBLI, C'EST UNE RÉGRESSION DE LA 4.19.43 (180412b, « on entre dans le chat sur place, deux onglets au lieu de trois »). Avant, l'onglet du chat était créé par le lien nommé, donc dans le même groupe de contextes : la bascule fonctionnait. Le passage à « le chat s'ouvre sur place » a coupé ce lien de parenté, et personne ne l'a rattrapé.
⚠⚠⚠ ET LE BANC EST RESTÉ VERT, PARCE QU'IL MESURE DU HTML. Web/tests/test-chat-onglet.php exige _blank + noopener (4 constats) et admet lui-même « ce banc ne sait pas exécuter du JavaScript ». Il code donc l'ancienne règle et la protège. C'est le motif « banc dont le monde ne contient pas le défaut » : il faudrait deux onglets et un vrai clic.
✅ CORRIGÉ LE 23/08/2026, en 4.19.50. Les liens internes de la page du chat reçoivent le nom d'un onglet compagnon, site_compagnon_onglet(), dérivé de APP_NAME comme l'est celui du chat. Les liens externes gardent _blank + noopener — leur donner notre nom les mettrait dans notre groupe de contextes.
Le banc a été réécrit en même temps : ses quatre constats codaient l'ancienne règle. Trois constats neufs gardent la moitié qu'il ne fallait pas corriger — un lien externe reste anonyme, un lien en // aussi, et le nom du compagnon doit différer de celui du chat. Trois mutations attrapées : _blank sur les internes (6 échecs), détection d'externalité neutralisée (3 échecs), nom du compagnon rendu égal à _blank (3 échecs). ⚠ Le compte de constats qui figurait ici a été retiré le 25/08/2026 — il annonçait 45 quand le banc en portait déjà 46, et il se périmait à chaque passe. php Web/tests/test-chat-onglet.php le donne.
⚠⚠ ET LE CORRECTIF DE 4.19.50 ÉTAIT INCOMPLET — RATTRAPÉ LE 23/08 EN 4.19.51 (450e4b0). Ce paragraphe s'arrêtait à « corrigé », et c'était sous-dire : deux liens internes de la page du chat portaient _blank ÉCRIT EN DUR — « Charte complète » et « Règlement des salons », dans un <details> de chat.php. La passe de sortie laisse intact tout ce qui porte déjà un target : c'est volontaire, mais cela veut dire qu'un _blank écrit à la main lui échappe entièrement. Le défaut du point 47 leur survivait donc.
⚠⚠⚠ ET LE BANC RÉÉCRIT NE LES VOYAIT PAS NON PLUS. Son constat de rendu ne les atteint pas — ils vivent dans un <details> que le rendu du banc ne produit pas. Ce sont 44 liens sur l'onglet compagnon et 6 en _blank, comptés par un curl sur la production après déploiement, qui les ont trouvés. Un constat de source a été ajouté : aucun gabarit de Web/site/ ne doit écrire target="_blank" sur un href interne. Mutation : remettre _blank en dur sur ?p=charte → 1 échec, et c'est bien le constat de source qui l'attrape, pas celui du rendu. C'est exactement pourquoi il fallait les deux.
⚠⚠ CE QUI MANQUE, ET C'EST PRÉCISÉMENT CE QUI A LAISSÉ PASSER LE DÉFAUT : deux onglets et un vrai clic dans un navigateur. Le banc mesure du HTML rendu — il voit désormais le bon target, mais il ne peut pas voir si la bascule window.open('', NOM) retrouve enfin l'onglet du chat. Ce point reste jaune tant que ce geste n'est pas fait.
| Pour fermer | ✅ RIEN — CONSTATÉ FERMÉ le 25/08/2026 par SerVuS, dans un vrai navigateur. Le nom de fenêtre remplace _blank sur les liens internes ; les externes restent anonymes — mesuré sur la page servie, les seuls target="_blank" qui subsistent sont les trois liens GitHub. Le banc est réécrit et tient à 46 constats. Au navigateur : deux onglets au total quel que soit le nombre de clics, et le retour au chat bascule sans recharger ni redemander le pseudo. ⚠⚠ ET LE FILET A ÉTÉ POSÉ LE MÊME JOUR — il manquait, et c'était le reliquat du point. Le verrou n'était consulté qu'au submit : par un favori, une URL tapée ou un ctrl+clic — les chemins que le nommage ne couvre pas — on saisissait pseudo, âge et ville, et c'est seulement en validant qu'on apprenait que le chat était ouvert ailleurs. L'ordre absurde que ce point dénonçait lui-même. Il est désormais consulté au chargement : constaté par SerVuS, l'avertissement tombe immédiatement et le formulaire reste utilisable. ⚠ On avertit, on ne bloque pas, et c'est délibéré : un onglet tué brutalement ne rend aucun verrou, et barrer l'entrée sur ce seul signe enfermerait dehors quelqu'un dont l'autre onglet n'existe plus. Le refus ferme reste au submit, là où le verrou est relu plus frais. |
|---|
⚠ Effet de bord à assumer : l'onglet compagnon sera réutilisé — lire la charte puis le règlement ne laissera plus les deux ouverts.
46. ✅ La touche Tab ne complétait pas les pseudos ni les salons — CONSTATÉ FERMÉ le 25/08/2026, au navigateur
Signalé par SerVuS le 23/08/2026 — « le Tab ne prend plus pour sélectionner Pseudo ou Salon .. ça sélectionne autre chose ». Précisé par lui : dans le champ de message, une fois connecté.
Dans tout client IRC, Tab complète un pseudo ou un #salon en cours de frappe. Dans le webchat, cette complétion n'existe pas : le seul gestionnaire du champ de saisie ne connaît qu'Enter.
el.saisie.addEventListener('keydown', function (ev) {
if (ev.key === 'Enter' && !ev.shiftKey) { ev.preventDefault(); envoieMessage(); }
});
Tab retombe donc sur le comportement natif du navigateur et le focus saute au bouton Envoyer, adjacent dans le DOM — « ça sélectionne autre chose ».
⚠ C'est un manque, pas une régression : git log -S"'Tab'" sur chat.js ne rend aucun commit. La complétion n'a jamais existé ici ; la comparaison porte sur un client IRC classique.
⚠ Les cinq causes classiques ont toutes été écartées mécaniquement avant d'en arriver là — aucun tabindex positif dans chat.php, aucun gestionnaire qui intercepte Tab, aucun réordonnancement CSS du formulaire, aucun élément focusable invisible, et l'ordre DOM Pseudo → Salon intact aux commits des 22 et 23/08.
✅ ÉCRIT LE 24/08/2026, en 4.19.52. Tab complète le mot sous le curseur : les présents du salon actif, ou les salons quand le mot commence par # ou &. Appuis successifs = cycle. Suffixe : en tête de ligne seulement, ailleurs. Shift+Tab reste le chemin de sortie clavier, et preventDefault() n'est appelé que si une complétion a eu lieu — sinon la touche paraîtrait morte sur un champ vide.
⚠ Deux fonctions PURES, et c'est ce qui rend la chose mesurable : motSousCurseur() et candidatsPour() ne touchent ni au DOM ni à l'état. Le banc neuf Web/tests/test-chat-completion.php les extrait et les fait tourner sous node, comme le font déjà test-chat-mention.php et test-chat-encodage.php.
⚠ Je me trompais en écrivant que « un banc PHP ne verra rien » : les bancs de ce projet savent exécuter du JavaScript. Ce qu'ils ne voient pas, c'est le branchement clavier — que Tab soit intercepté, que le focus ne saute plus au bouton « Envoyer ». La distinction est écrite en tête du banc.
41 constats au vert, trois mutations attrapées : retirer la garde « dièse en tête de mot » (1 échec), rendre la comparaison sensible à la casse (6 échecs), et construire une regex depuis le préfixe — celle-là fait échouer node lui-même, la regex ^[z étant invalide. C'est la démonstration du commentaire que le projet répète depuis toujours : un pseudo contient des caractères qu'une regex interprète.
⚠ Un défaut trouvé dans mon propre code en écrivant les cas du banc : le # était rattaché même précédé d'une lettre, alors que le commentaire affirmait le contraire. « a#b » aurait été pris pour un salon. Corrigé avant que le banc soit écrit, et c'est ce cas que la première mutation garde.
⚠⚠ CE QUI MANQUE : le geste au navigateur. Taper un début de pseudo, presser Tab, voir la complétion se poser et le focus rester dans le champ. Ce point reste jaune tant qu'il n'est pas fait.
| Pour fermer | ✅ RIEN — CONSTATÉ FERMÉ le 25/08/2026 par SerVuS, dans un vrai navigateur. C'est le seul constat qui vaille ici : le banc PHP ne voit ni frappe, ni focus, ni cycle. Ce qu'il a nommé explicitement : la complétion prend sur les pseudos et sur les salons quand le mot commence par #, et le suffixe : apparaît bien en début de ligne. Le reste du critère — cycle sur appuis successifs, et Shift+Tab comme sortie clavier — est couvert par son « tout est bon », en réponse à la question qui les énumérait un à un. ⚠ La sortie clavier était la moitié qui comptait : sans elle, un champ qui capture Tab devient un piège à focus pour qui navigue sans souris. |
|---|
⚠ Trouvé au passage, sans rapport avec ce signalement : site.css remonte le bloc « Appeler un opérateur » en tête de colonne par order: -1, alors qu'il est en dernier dans le DOM. La seule commande « dont on a besoin dans l'urgence » est donc atteinte en dernier au clavier — contradiction avec l'intention écrite juste au-dessus. Correctif : déplacer le bloc dans le DOM et retirer le order.
45. ✅ make install d'Eggdrop visait son propre répertoire source, et tcl-tls manquait — CONSTATÉ FERMÉ le 24/08/2026 sur une installation neuve
Trouvé le 23/08/2026 en montant une Ubuntu 24.04 neuve pour les points 26 et 42. Eggdrop compile parfaitement — binaire 1,8 Mo, 18 modules .so — puis l'installation s'arrête net :
You are trying to install into the source directory.
That will not work. Please specify a different install directory with:
make install DEST="/home/wcc/mybot"
make: *** [Makefile:336: install-start] Error 1
✖ Installation échouée → rollback.
La cause, et elle est structurelle, dans Bootstrap/overtchat-setup.sh :
| Ligne | Ce qui se passe |
|---|---|
_fetch_sources | tar -xzf "$archive" -C "$dir" --strip-components=1 → les sources atterrissent dans $dir |
| bloc d'installation | cd "$dir" puis make install DEST="$dir" → DEST est le répertoire source |
Eggdrop refuse ce cas par conception, depuis son propre Makefile. Ce n'est donc ni une affaire de distribution, ni de droits, ni d'Ubuntu : cela échouera sur n'importe quelle machine, et le rollback efface les traces derrière lui.
⚠ Conséquence en cascade : sans Eggdrop, l'écran IriX reste verrouillé (« Eggdrop doit être installé d'abord — IriX est un module Tcl qu'Eggdrop héberge »), et avec lui OverOp. L'installateur ne peut donc installer aucun des produits du projet sur une machine neuve.
⚠ Le commentaire juste au-dessus du bloc dit « Constaté sur une installation neuve menée de bout en bout le 29/07/2026 ». Soit le contrôle d'Eggdrop s'est durci depuis — la version retenue est une RC, 1.10.2+RC1, et le script prévient lui-même que c'est « la première chose à remettre en cause » — soit ce chemin n'a plus été repris depuis. À trancher avant de corriger.
Correctif éprouvé le 23/08 (dans une copie d'essai, pas au dépôt) : compiler dans un sous-répertoire et n'installer que dans $dir.
srcdir="${dir}/.src-build"
rm -rf "$srcdir"; mkdir -p "$srcdir" || exit 1
shopt -s dotglob
for f in "$dir"/*; do [[ "$f" == "$srcdir" ]] && continue; mv "$f" "$srcdir"/ || exit 1; done
shopt -u dotglob
cd "$srcdir" || exit 1
./configure && make config && make -j"$(_make_jobs)" && make install DEST="$dir"
Avec lui, l'installation aboutit : eggdrop -> eggdrop-1.10.2 (1 839 648 o), 18 modules, help/, language/, filesys/, et .installed posé. C'est ce qui a permis de fermer le point 26.
⚠⚠ ET IL EN MANQUE UN SECOND, DU MÊME MOTIF : tcl-tls. Mesuré le 23/08 : une fois Eggdrop installé et IriX déployé, le démarrage meurt sur
can't find package tls
while executing "package require tls"
(file ".../IriX/cmds/IriX-sql.tcl" line 10)
* FICHIER DE CONFIGURATION NON CHARGÉ
grep -c "tcl-tls" sur overtchat-setup.sh rend 0 : le paquet n'est déclaré nulle part, alors que IriX-sql.tcl l'exige dès sa dixième ligne. La production, elle, porte tcl-tls 1.8.0-2 — posé à la main, ce qui explique que personne ne l'ait vu. Les dépendances déclarées pour IriX sont tcl tcllib mysqltcl default-mysql-client ; il y manque tcl-tls.
✅ LE TROISIÈME ÉCART EST TRANCHÉ ET CORRIGÉ le 24/08/2026, en 4.19.55. default-mysql-client mène à MariaDB sur Debian mais tire MySQL 8 sur Ubuntu, qui entre en conflit avec mariadb-server. Or la famille debian de l'installateur couvre les deux : Ubuntu y est rangé par ID_LIKE. Un exploitant Ubuntu obtenait donc un client MySQL et ne pouvait plus poser le serveur que tout le projet suppose.
SerVuS a tranché : nommer mariadb-client. ⚠ Les quatre autres familles le nommaient déjà explicitement — debian était la seule à passer par un métapaquet ambigu. Mesuré avant de changer : le paquet est déjà installé en production (Debian 13, 1:11.8.6) et disponible sur Ubuntu (1:10.11.14).
⚠ Cinq constats neufs exigent que chaque famille nomme un paquet MariaDB — sans quoi le défaut se reformerait sur une autre distribution. Mutation : remettre le métapaquet → 2 échecs.
✅ CONSTATÉ sur l'Ubuntu le 24/08/2026, en repartant du point de départ fautif — MySQL 8 installé, mariadb-client absent :
IriX ◐ incomplet
↳ mariadb-client ← l'écran réclame le bon paquet
Removing default-mysql-client … ← l'installation retire MySQL 8
Removing mysql-client-8.0 …
Setting up mariadb-client (1:10.11.14-0ubuntu0.24.04.1) …
État final : mysql pointe vers /usr/bin/mariadb, MariaDB 10.11.14, et mysql-client-8.0 n'est plus installé. Le défaut est éteint là où il vivait.
| ✅ CONSTATÉ FERMÉ | Le 24/08/2026, sur une installation neuve de l'Ubuntu 24.04, avec l'installateur retéléchargé depuis le site — donc sans le moindre correctif local. Les deux causes sont éteintes. |
|---|
Le point de départ a été rendu mordant : tcl-tls a d'abord été retiré de la machine, et tclsh vérifié incapable de charger tls. Sans cela l'épreuve aurait été creuse — le paquet y avait été posé à la main la veille.
L'installateur téléchargé est bien celui du dépôt : même empreinte fa9bc835…, .src-build présent, tcl-tls cité 9 fois.
| Ce qui devait se produire | Mesuré |
|---|---|
tcl-tls demandé, puis posé | « Paquets manquants : tcl-tls … » → Setting up tcl-tls (1.7.22-3build2) |
Tcl sait de nouveau charger tls | OUI — la capacité, pas seulement le nom du paquet |
| Eggdrop s'installe sans correctif local | binaire eggdrop-1.10.2 (1 839 632 o), 18 modules, .installed posé |
| les sources ne sont plus dans le répertoire d'installation | .src-build présent |
| l'écran IriX se déverrouille | [2] IriX Services sans message de blocage |
⚠ Ce que ce constat n'englobe pas : le troisième écart, default-mysql-client qui tire MySQL 8 sur Ubuntu au lieu de MariaDB. Il est réapparu pendant cette même passe — « Paquets manquants : … mysql-client-8.0 » — et il reste à trancher, pas à corriger.
✅ LES DEUX CORRECTIFS SONT PORTÉS AU DÉPÔT LE 24/08/2026, en 4.19.53. Jusque-là ils n'existaient que dans une copie d'essai — et le commit du 23/08 qui porte le nom de ce point n'avait touché que ce registre. C'est un écart que la mesure a rattrapé : grep -c "tcl-tls" sur l'installateur rendait encore 0.
| Ce qui est porté | Comment |
|---|---|
| Eggdrop compile à côté de son installation | les sources sont déplacées dans $dir/.src-build, make install DEST="$dir" vise le répertoire que la suite attend |
tcl-tls est déclaré | ajouté à _req_for irix, traduit dans les cinq familles de paquets |
| un nom faux ne ment plus | _pkg_present teste la capacité — il demande à Tcl s'il sait charger tls — et une famille inconnue le déclare manquant, jamais présent |
⚠ Seul le nom Debian/Ubuntu est MESURÉ (tcl-tls). Les quatre autres sont probables ; c'est le repli par capacité qui rattrape une erreur de nom, exactement comme la leçon déjà écrite pour mysqltcl juste au-dessus dans _pkg_present : répondre « présent » sans savoir est le pire des choix.
Le banc Bootstrap/tests/test-deps-eggdrop.sh garde les deux causes — 14 vérifications, trois mutations attrapées : retirer tcl-tls (2 échecs), recompiler dans le répertoire des sources (2 échecs, dont le constat central), retirer le repli par capacité (1 échec).
⚠⚠ ET CE BANC S'EST FAIT PRENDRE EN S'ÉCRIVANT. Son constat sur make install comptait 2 au lieu de 1 : le vrai appel, et l'exemple cité dans le commentaire que le correctif venait de poser juste au-dessus. Un banc qui lit un commentaire au lieu du code croit mesurer ce qu'il ne mesure pas. Un grep -v '^\s*#' suffit, et il est commenté sur place pour que personne ne le retire.
⏪ CE QUI SUIT EST L'ÉTAT D'AVANT LA FERMETURE — PÉRIMÉ, gardé pour la trace. Le critère décrit ci-dessous a été constaté le 24/08/2026 — voir l'encadré ✅ en tête de point. L'installation neuve a bien eu lieu sur l'Ubuntu 24.04, avec l'installateur retéléchargé depuis le site, donc sans le moindre correctif local. ⚠ Ne pas le relire comme du travail à faire. Un encadré « Pour fermer » périmé sous un titre ✅ est exactement ce qui fait rouvrir un point déjà clos, ou croire ouvert ce qui ne l'est plus.
⚠ CE QUI MANQUE : l'installation neuve de bout en bout, sans correctif local — l'installateur retéléchargé depuis le site, Eggdrop qui passe à « installé », IriX qui se déverrouille et démarre. L'Ubuntu-24.04 est en place pour ça. Le troisième écart — default-mysql-client qui tire MySQL 8 sur Ubuntu au lieu de MariaDB — reste à trancher, pas à corriger.
| Pour fermer | Une installation neuve où l'écran Eggdrop passe à « installé » sans correctif local, et où IriX se déverrouille dans la foulée. ⚠ Vérifier le binaire et les modules : un binaire sans modules/*.so se lance puis refuse tout bind msg — c'est le piège déjà rencontré le 29/07. |
|---|
44. ✅ La table d'audit du site gardait des adresses IP sans aucune purge — CONSTATÉ FERMÉ le 23/08/2026
✅✅ CONSTATÉ FERMÉ LE 23/08/2026 À 14:00, de bout en bout, sur la production.
La table ne portait rien de plus de seize jours : la purge aurait été inerte jusqu'en novembre, et l'on n'aurait pas su distinguer « rien à purger » de « refusé en silence ». Une ligne volontairement vieillie de 100 jours a donc été posée, puis le scan attendu. Le journal du robot :
2026-08-23T14:00:00 [INFO] audit: purge — 1 ligne(s) retirée(s), 22 gardée(s)
23 lignes avant, 22 après, la témoin partie et elle seule. Les deux comptages copiés sur ::soc::hist::purger sont ce qui rend ce constat lisible.
⚠⚠ DEUX ERREURS DE MÉTHODE, RATTRAPÉES PAR LE CONSTAT ET PAR RIEN D'AUTRE :
- L'appel avait atterri dans
::soc::scan::hourly, pas dans le scan quotidien. Le bloc pris pour la fin de l'un était la fin de l'autre — ancrer sur du code sans vérifier dans quelle procédure il vit. Le hasard fait bien les choses : c'est le bon endroit, les purges par ancienneté y sont déjà. Mais je l'ai su en essayant de constater, pas en écrivant. - « scan quotidien » rend ZÉRO dans
IriX.loget QUATRE dansIriX-sql.log. J'ai failli conclure que le scan ne tournait jamais. La règle des trois journaux a payé, et c'est la première fois qu'elle sert à éviter une conclusion fausse plutôt qu'à trouver une trace.
⚠ Et un piège d'outillage : sudo systemctl restart irix bloque cinq minutes sur une invite de mot de passe, quand la règle NOPASSWD du sudoers exige irix.service, au caractère près. Le nom d'unité doit être écrit exactement comme dans la règle.
⚠ La charte annonce désormais 90 jours, là où elle avouait le manque. Changer la durée dans le code sans la porter à la charte fait rougir test-charte-retention.php, et c'est voulu.
Trouvé en corrigeant la charte, et c'est la charte elle-même qui l'annonce désormais au visiteur : « les traces d'administration du site ne sont pas encore purgées automatiquement ». L'écrire oblige à la poser — c'est pourquoi ce point existe.
IriX_web_audit porte une colonne ip en VARCHAR(45), remplie par audit_log() avec l'adresse entière du visiteur, jamais tronquée. La migration Web/sql/019-web-audit.sql le dit en toutes lettres : « Aucune purge automatique dans cette migration… Si elle grossit, la purge sera une décision d'exploitation. » Cette décision n'a jamais été prise.
⚠ Ce n'est pas une table technique : c'est un journal d'actions d'administration indexé par adresse IP. Toutes les autres données personnelles du projet ont une échéance — sessions 30 jours, événements 90 jours, messages publics 30 jours. Celle-ci est la seule sans fin.
⚠ Le plafond de l'ASV en dépend, et il faut y penser en posant la purge. asv_rate_block() compte les refus par un COUNT(*) sur cette même table, fenêtre d'une heure. Une purge qui effacerait trop court viderait le compteur et libérerait les blocages ; toute échéance largement supérieure à l'heure est sans effet sur lui.
⚠ Deux questions à trancher avant d'écrire :
- Quelle durée ? 90 jours alignerait sur les événements et les décisions de modération, qui sont la même nature de trace. ⚠ Toute valeur choisie devra être portée à la charte —
test-charte-retention.phprougira sinon, et c'est voulu. - Qui la lance ? Les autres purges vivent dans le scan quotidien du bot (
IriX-sql.tcl), qui ne connaît pas les tables du site. Soit on l'y ajoute, soit le site se purge lui-même — mais il n'a aucun mécanisme périodique aujourd'hui, ce qui ferait de ce point un chantier plus large qu'il n'en a l'air.
| ✅ CONSTATÉ FERMÉ | Le 23/08/2026 à 14:00, sur la production : une ligne vieillie de 100 jours posée exprès, le scan horaire attendu, 23 lignes avant, 22 après, et le journal qui le dit — « audit: purge — 1 ligne(s) retirée(s), 22 gardée(s) ». La durée est annoncée à la charte et un banc la croise avec le code. |
|---|
43. ✅ Quatre familles de fichiers système n'atteignaient AUCUNE installation tierce — CONSTATÉ FERMÉ le 24/08/2026
✅ LA QUATRIÈME FAMILLE — APACHE — EST ÉCRITE le 21/08/2026, écran « Services système », action 4. Elle pose et n'active RIEN, et c'est la décision qui commande tout le reste :
a2ensitesuririx-ssl.confsans certificat empêche Apache de démarrer, et c'est tout le site qui tombe, pas seulement ce vhost. L'activation reste un geste de l'exploitant, et l'installateur l'écrit à l'écran plutôt que de le faire.⚠⚠ Et il faut savoir ce que le contrôle NE VOIT PAS. Un vhost posé dans
sites-available/sansa2ensiten'est lu par personne — pas même parapache2ctl configtest, qui n'inclut quesites-enabled/. Le seul fichier de cette famille que le contrôle voie réellement estports.conf. Croire que le configtest garde les vhosts serait se croire gardé sans l'être.⚠⚠
ports.confN'EST PAS À NOUS, et c'est le seul de tout ce chantier dans ce cas : il appartient au paquet Debian, et le poser l'écrase. L'installateur en garde donc l'original — et deux gardes le protègent, parce qu'une seule ne suffisait pas :
- une seule fois : la pose est ré-exécutable, et une sauvegarde reprise au second passage ne sauvegarderait plus que notre propre fichier ;
- ⚠ « déjà le nôtre », par comparaison de contenu — trouvé le 21/08 en mesurant la production, où
ports.confavait été posé à la main en juillet. La garde 1 ne couvrait pas ce cas : le premier passage aurait écrit un fichier nommé « avant-overtchat » contenant l'après, et la désinstallation aurait rendu à Apache une configuration qui n'est pas la sienne, en croyant bien faire.⚠ L'état de départ est mesuré AVANT de toucher à quoi que ce soit. Un
configtestdéjà rouge avant la pose ne lui est pas imputé : on ne replie que ce qu'on a fait passer du vert au rouge. Sans cela, une configuration cassée par ailleurs enverrait chercher un défaut dans du code juste.⚠ La table porte des MOTIFS, pas un dossier — les trois destinations l'imposent. Ce qui remplace la dérivation : un fichier de
Config/apache/qu'aucune règle ne réclame est refusé et dit, jamais posé au hasard. Ajouter un vhostirix-*.confreste couvert tout seul.Le désinstalleur a été mis en accord : il ne supprime jamais
ports.conf— il le restaure, ou dit qu'il ne le peut pas ; il retiremaintenance.htmlet son dossier ; et son rechargement d'Apache, jusque-là inconditionnel, passe désormais par unconfigtest.Banc :
Bootstrap/tests/test-systeme.sh— 64 constats, il exécute les deux poses sur un faux/etc(élargi à une racine entière,/var/wwwn'étant pas sous/etc), et sept mutations rougissent, dont les quatre qui comptent : retirer la sauvegarde, retirer l'une ou l'autre de ses deux gardes, activer un vhost, et ignorer l'état de départ.⚠⚠ Deux constats ont d'abord été écrits FAUX, et la passe de mutation les a trouvés : l'un ne rougissait pour aucune mutation (la clause de contenu masquait la garde « une seule fois », qui n'était plus interrogée du tout), l'autre jouait un scénario qui ne pouvait pas mordre. Un banc neuf sans passe de mutation n'aurait rien gardé, en affichant tout vert.
🟡 Ce qui reste, et c'est un geste de l'exploitant : LE CONSTAT. Aucune des deux poses n'a été lancée sur un
/etcécrivable — écrire là exige un mot de passe,sudo -nne couvrant quesystemctl. ⚠ En production, tout est déjà en place et activé (posé à la main en juillet et en août) : la pose y sera donc une re-pose, sans rien à activer. Deux écarts s'y corrigeront au passage —irix-admin.confetirix-ssl.confy sont en 755.⚠⚠ « TOUT EST DÉJÀ EN PLACE » EST FAUX, ET L'INSTALLATEUR LE SAVAIT AVANT CE REGISTRE. Remesuré le 23/08/2026 à 18:15 : trois des quatre familles seulement sont posées, et polkit ne l'est pas du tout —
/etc/polkit-1n'existe pas sur cette machine, ni sonrules.d.Config/polkit/10-overtchat-irix.rulesvit au dépôt et n'a jamais atteint/etc. Le commentaire de_sys_dest_prete, dansBootstrap/overtchat-setup.sh, le porte noir sur blanc depuis le 20/08 : «/etc/polkit-1n'existe pas sur la machine de production — polkit n'y est simplement pas installé ». Le code avait donc raison pendant que ce point disait le contraire.⚠ Conséquence pour le constat, et elle change le geste :
_sys_dest_preteteste la racine du logiciel et saute proprement une famille dont le logiciel est absent. Sur CETTE machine, l'action 3 posera donc sudoers et logrotate, et ne touchera jamais polkit. La pose polkit ne peut pas être constatée ici : il faut une machine où polkit existe.
Marqueur Mesuré le 23/08 /etc/polkit-1/rules.d/10-overtchat-irix.rulesle répertoire lui-même n'existe pas /etc/apache2/ports.conf.avant-overtchatabsent — seul ports.conf(22/07 13:10)/etc/apache2/sites-available/irix-admin.conf-rwxr-xr-x— 755, 21/07 21:10/etc/apache2/sites-available/irix-ssl.conf-rwxr-xr-x— 755, 13/08 20:54/etc/sudoers.d/overtchat-servicesprésent, 440, 31/07 13:13 — pose manuelle/etc/logrotate.d/overtchatprésent, 28/07 21:20 — pose manuelle ⚠ Aucun horodatage sous
/etcn'est postérieur au 13/08 pour ces familles : l'installateur n'a jamais tourné contre le vrai/etc. Et le désinstalleur sait retirer un fichier polkit qui n'a jamais existé ici — ce chemin-là non plus n'a jamais été parcouru en vrai.
✅ CONSTATÉ FERMÉ Le 24/08/2026, sur l'installation neuve de l'Ubuntu 24.04, avec l'archive et l'installateur retéléchargés du site. Les fichiers arrivent, ils sont adaptés, et Apache reste sain.
L'écran compte enfin, là où il affichait 0/0 depuis toujours :
Unités livrées 14
Fichiers système 0 / 3 sudoers · polkit · logrotate
Fichiers Apache 1 / 5 posés inactifs
Et les fichiers posés sont ADAPTÉS — le compte de service visé était root, propriétaire de la racine d'installation :
| Fichier posé | Ce qu'il porte |
|---|---|
/etc/sudoers.d/overtchat-services (440) | Defaults:root, root ALL=(root) NOPASSWD: |
/etc/polkit-1/rules.d/10-overtchat-irix.rules (644) | subject.user === "root" — et www-data intact |
/etc/logrotate.d/overtchat (644) | create 0640 root root |
⚠⚠ Le commentaire est épargné : grep -c servus sur le sudoers posé rend 2 — les deux occurrences qui restent sont dans des commentaires. C'est la preuve que les motifs sont nommés et qu'aucun remplacement global n'est passé.
La garde Apache mord, et elle mord juste : irix-default.conf, irix-ssl.conf et maintenance.html sont IGNORÉS (« modèle NON adapté à cette installation »), tandis qu'irix-admin.conf et ports.conf, qui ne portent aucun marqueur de machine, sont posés. Résultat annoncé par l'installateur : « 2 posé(s), 3 refusé(s) — AUCUN n'est activé », et apache2ctl configtest : toujours vert. L'original du paquet est conservé en ports.conf.avant-overtchat.
⚠ Un faux départ, et il vaut d'être noté : le premier essai a montré le sudoers posé sans substitution. Ce n'était pas le code — l'installateur de l'essai avait été téléchargé avant le push de 4.19.54. Un constat pris sur une copie périmée dit le contraire de la vérité, et rien ne le signale : il faut vérifier la version de ce qu'on éprouve, pas seulement ce qu'on éprouve.
✅ TRANCHÉ ET FAIT LE 24/08/2026, en 4.19.54 — SerVuS a choisi de DISTRIBUER. Ce qui suit reste écrit parce qu'il explique pourquoi le geste a la forme qu'il a.
La livraison passe par l'ARCHIVE, pas par une liste. Les cinq dossiers sont ajoutés au tar de l'archive site, qui s'extrait déjà dans $BASE_DIR — c'est-à-dire là même où _sysd_dir() et le find Config/apache vont chercher.
⚠⚠ POURQUOI PAS UNE LISTE, QUI ÉTAIT LA VOIE ÉVIDENTE : le code qui POSE ces fichiers dérive sa liste du disque, et trois commentaires l'imposent — « LA LISTE EST DÉRIVÉE de Config/systemd/, jamais énumérée ». Énumérer les trente fichiers aurait réintroduit exactement la liste centrale qu'ils interdisent, et un fichier ajouté demain aurait été posé ici et absent chez un tiers, en silence.
⚠ La surveillance de fraîcheur suit, par une seconde constante employée aux deux endroits — le tar et le calcul qui décide de régénérer. Un dossier ajouté au seul tar serait emporté sans jamais être surveillé : c'est le défaut corrigé le 10/08 pour les fichiers de la racine, que le docbloc interdit de recréer.
LA LIVRAISON NE SUFFISAIT PAS, ET C'EST LE POINT LE MOINS ÉVIDENT. _sys_poser faisait un install brut. Posés tels quels, le sudoers et le polkit auraient accordé des droits à « servus », qui n'existe pas chez un tiers. L'installation aurait eu l'air configurée sans l'être — pire que l'état d'avant, où elle annonçait honnêtement 0/0. _sys_substituer() adapte donc les trois familles mécaniques, avec la règle de _sysd_substituer() : des motifs nommés, jamais un remplacement global qui réécrirait les commentaires.
⚠⚠ APACHE EST LIVRÉ MAIS NON ADAPTÉ, et une garde neuve REFUSE de le poser sur une machine dont le compte diffère. irix-ssl.conf porte un ServerName et des chemins de certificats en dur : posé tel quel, il en désigne des absents et Apache refuse de démarrer. Mieux vaut ne rien poser que casser le serveur web de quelqu'un. La garde ne mord que si le compte diffère — ici, la re-pose reste possible.
Éprouvé : test-systeme.sh passe de 62 à 73 vérifications, test-distrib.php reste à 25. Deux mutations, deux attrapées : un remplacement global (pris par le constat qui vérifie qu'un commentaire n'est pas réécrit), et la pose du modèle brut.
⏪ CE QUI SUIT EST L'ÉTAT D'AVANT LA FERMETURE — PÉRIMÉ, gardé pour la trace. Le critère décrit ci-dessous a été constaté le 24/08/2026 — voir l'encadré ✅ en tête de point. Les fichiers arrivent, ils sont adaptés, et Apache reste sain — mesuré sur l'installation neuve avec l'archive et l'installateur retéléchargés du site. ⚠ Le « CE POINT NE PEUT PAS SE FERMER DU TOUT » qui suit date du 23/08 : il a été levé le lendemain, et c'est justement ce qu'il annonçait impossible qui a été constaté. ⚠ Ne pas le relire comme du travail à faire. Un encadré « Pour fermer » périmé sous un titre ✅ est exactement ce qui fait rouvrir un point déjà clos, ou croire ouvert ce qui ne l'est plus.
⚠ CE QUI MANQUE : le constat sur une installation tierce — les fichiers qui arrivent vraiment, et le sudoers posé au nom du bon compte.
⚠ Ces familles restent exclues de la mise à jour. Distribuer permet la première pose ; la re-pose demeure un geste manuel — le code le disait déjà pour systemd.
⚠⚠⚠ ET CE POINT NE PEUT PAS SE FERMER DU TOUT — MESURÉ LE 23/08/2026 SUR UNE UBUNTU 24.04 NEUVE. L'écran « Services système » d'une installation tierce affiche
0/0sur les quatre familles, et il l'affichera toujours : les fichiers à poser n'arrivent jamais. Quatre voies, quatre fermées.
Voie Mesuré l'installateur écrit-il dans Config/{systemd,polkit,sudoers.d,logrotate.d,apache}?aucune écriture — que des lectures le site les distribue-t-il ? 404 sur les cinq essayés, quand overtchat-setup.shetirix-mandat.tclrendent 200l'archive /dl/siteles contient-elle ?non — son Config/ne porte queidentity.json.exampleetvilles.txtun tiers peut-il cloner le dépôt ? non — Repository not found: le dépôt est privé⚠ Le critère « lancer les poses sur un
/etcécrivable » n'est donc réalisable que sur une machine qui possède déjà le dépôt — la production. Et là, polkit est absent, donc cette famille-là est sautée. Ni ici, ni ailleurs.Deux issues, et elles ne se valent pas : distribuer ces fichiers, en les mettant au manifeste comme le reste — et le point redevient réalisable ; ou écrire noir sur blanc que l'installateur suppose un dépôt local, prérequis qui n'est aujourd'hui énoncé nulle part — et alors ce point doit être reformulé, pas fermé.
✅ sudoers, polkit et logrotate sont posés par l'installateur (4.19.32), écran « Services système », action 3. Liste dérivée, pose ré-exécutable, et l'ancien
/etc/sudoers.d/overtchat-irixretiré — son remplaçant le demandait dans son propre en-tête.⚠⚠ Le sudoers est CONTRÔLÉ avant d'être posé, jamais après.
visudo -c -fsur le fichier source ; s'il ne passe pas, il n'est pas posé — et les deux autres familles le sont quand même, car un refus ne doit pas tout arrêter. C'est la seule pose de ce chantier qui puisse casser autre chose que le projet.⚠ La table porte des DOSSIERS, pas des fichiers. Ajouter un fichier dans
Config/sudoers.d/est couvert automatiquement ; seul l'ajout d'un dossier destiné à/etcdemande d'y toucher, et le banc le dit alors.Banc :
Bootstrap/tests/test-systeme.sh— il exécute la pose sur un faux/etc, et les mutations rougissent : ne rien poser, poser le sudoers en 644 (sudo l'ignorerait en silence), le poser sans le contrôler, perdre une famille, et ignorer le verdict devisudo.
L'état d'origine, gardé parce qu'il explique la décision
Sorti du point 41 le 20/08/2026, une fois les unités systemd posées. Le trou était plus large que les unités, et c'est la même forme : Tools/desinstaller.sh:175-181 sait retirer quatre familles de fichiers système que l'installateur ne pose jamais.
| Fichier | Ce qu'il porte | Ce qui casse sans lui |
|---|---|---|
/etc/sudoers.d/overtchat-services | les droits NOPASSWD sur systemctl | ⚠⚠ la veille du point 40 CONSTATE mais ne peut pas GUÉRIR — elle voit IriX mort et n'a pas le droit de le relancer |
/etc/polkit-1/rules.d/10-overtchat-irix.rules | l'équivalent pour irix.service | le panel web ne peut plus agir sur le bot |
/etc/logrotate.d/overtchat | la rotation des journaux | les journaux enflent sans limite — ⚠ c'est la troisième piste du point 38 |
/etc/apache2/sites-available/irix-*.conf | les vhosts du site | le site n'est pas servi |
⚠ Le plus grave est le premier, et il se lit dans l'unité elle-même (Config/systemd/overtchat-veille.service) : « Sans ce droit, la veille alerte quand même : elle constate et écrit, elle ne peut simplement pas guérir. » Sur une installation neuve, la surveillance qu'on vient de livrer serait donc à moitié infirme, sans que rien ne le dise.
Pour fermer : reprendre le motif déjà éprouvé au point 41 — liste dérivée de Config/, substitution des chemins, pose ré-exécutable, et le banc qui compare les deux sens. ⚠ Trois différences avec les unités, qui ne sont pas des détails :
sudoers.dexigevisudo -cavant la pose, et un mode440. Un fichier sudoers invalide peut rendresudoinutilisable sur toute la machine — c'est la seule pose de ce chantier qui puisse casser autre chose que le projet.- Les vhosts Apache demandent un
a2ensiteet un rechargement, pas une simple copie. polkitne demande aucun rechargement — son en-tête le dit déjà.
⚠ Et comme pour les unités : la mise à jour n'atteint pas /etc, donc la pose doit être ré-exécutable ou la première sera la dernière.
42. ✅ Le compte owner du bot naissait avec un masque sans ident — CONSTATÉ FERMÉ le 23/08/2026, après avoir trouvé que l'enrôlement était cassé
| ✅ CONSTATÉ FERMÉ | Le 23/08/2026 à 20:58:20, sur l'installation neuve, après correction du défaut ci-dessous. Le masque attendu est écrit, et les deux lignes de journal du critère sont là. |
|---|
Le userfile, écrit par le premier enrôlement :
SerVuS - jlmnoptx
--HOSTS ~servus@127.0.0.1
--HOSTS *!*servus@127.0.0.1
--PASS2 $pbkdf2-SHA256$rounds=16000$…
*!*servus@127.0.0.1 — exactement *!*<ident>@<domaine>, la forme que le correctif du 21/08 devait produire. Le ~ du masque brut est bien retiré.
⚠⚠ LES DEUX LIGNES DU CRITÈRE N'ÉTAIENT PAS DANS IriX.log — elles sont dans IriX/cmds/IriX-sql.log, le second journal :
2026-08-23T20:58:20 [INFO] SerVuS promu owner (premier enrôlement)
2026-08-23T20:58:20 [INFO] SerVuS Active HOSTS DCC (*!*servus@127.0.0.1)
2026-08-23T20:58:20 [INFO] reg: enregistrement SQL owner SerVuS!~servus@127.0.0.1 déclenché
2026-08-23T20:58:20 [INFO] reg: owner 'SerVuS' inséré (INSERT)
Un grep sur le seul IriX.log aurait conclu que le constat avait échoué. Le quatrième endroit confirme : IriX_users porte id 1, admin SerVuS, csowner oui, created 2026-08-23 20:58:20, mot de passe haché.
✅ LE DÉFAUT TROUVÉ EN PRENANT CE CONSTAT — CORRIGÉ ET ÉPROUVÉ LE MÊME JOUR. Ce qui suit est le récit de ce qui bloquait, gardé parce qu'il explique pourquoi un point « corrigé » a pu rester faux pendant deux jours.
🔴 LE PREMIER CONSTAT, LE 23/08/2026 À 20:46, NE FERMAIT PAS CE POINT — IL TROUVAIT PIRE. Sur une Ubuntu 24.04 neuve, base vide, userfile absent, le premier /msg IriX pass … rend :
Tcl error [::soc::reg::on_pass]: invalid command name "users"
Aucun masque n'est écrit. Aucun userfile n'est créé. Le compte owner ne naît pas. Le correctif du 21/08 porte donc sur du code qui n'a jamais pu s'exécuter : on_pass meurt à sa première ligne utile, bien avant d'arriver au calcul du masque.
La cause, une ligne, dans IriX/cmds/irix-register.tcl :
proc ::soc::reg::find_owner_on_irc {} {
set owner "*"
set users [users] ;# « users » n'est pas une commande Eggdrop
⚠ Le projet connaît le bon nom et l'écrit partout ailleurs : userlist. IriX-sql.tcl fait même foreach u [userlist n] — exactement ce que cette procédure veut faire. [users] n'apparaît qu'une fois dans tout le dépôt : ici.
⚠⚠⚠ ET LE BANC EST VERT PARCE QU'IL FABRIQUE LA COMMANDE MANQUANTE. IriX/tests/test-register.tcl charge le vrai find_owner_on_irc et le vrai on_pass par extraire — puis, quelques lignes plus haut, définit :
proc users {} { return $::userlist }
Le banc a donc créé le monde dans lequel le code est juste. C'est le motif « banc dont le MONDE ne contient pas le défaut », dans sa forme la plus pure : tout le reste du banc est excellent — il éprouve l'ident, le ~, le masque sans domaine, l'intrus, le mot de passe faux — mais rien de tout cela n'est jamais atteint en vrai.
⚠ Pourquoi la production ne l'a jamais vu : son owner existe depuis le début, donc on_pass n'y est jamais appelé. Ce point le disait déjà — « la production ne repassera jamais par ce chemin ». Il fallait une installation neuve pour le voir, et c'est précisément ce qui vient d'être fait.
Correctif appliqué le 23/08/2026 : set users [userlist] dans find_owner_on_irc, et le bouchon du banc renommé users → userlist, sinon il aurait continué de protéger le défaut suivant du même genre.
⚠ Passe de mutation, deux mutations, deux attrapées — sans quoi le vert ne prouvait rien :
| Mutation | Ce que le banc a fait |
|---|---|
remettre set users [users] | arrêt sur invalid command name "users", code 1 — l'erreur même de la production |
retirer le * de tête du masque (*!$ident@$domain) | 2 échecs sur 24 |
Restauré, le banc revient à 24 vérifications au vert.
D'abord le correctif ci-dessus. Ensuite le constat d'origine, qui restait entier au moment où ceci a été écrit : sur une installation neuve, un HOSTS de forme *!*<ident>@<domaine> écrit par on_pass, avec ses lignes <hand> Active HOSTS DCC (…) et <hand> promu owner (premier enrôlement). ⚠ Le banc ne le prouvera jamais : il faut une vraie installation. |
|---|
⚠⚠ LA MESURE A RETOURNÉ CE POINT, et dans les deux sens à la fois. Il sur-disait sur la gravité et sous-disait sur la portée. Ce qui suit est mesuré, pas déduit.
1. Les flags
n/one gardent AUCUNE commande d'IriX. Recensement fait sur toutIriX/:matchattrn'apparaît que dansirix-register.tcl, quatre fois, toutes sur l'enrôlement ; aucunbindne porte de masque de flags — lesbind dccd'IriX-binds.tclsont tous en-|-, et le fichier dit pourquoi : « la garde reste celle de la commande elle-même ». Les ~60 commandes passent par::soc::require_grade, qui lit la base et exigeauth='oui'.matchban: zéro occurrence.2. Ce que les flags gardent réellement, c'est la partyline Eggdrop — donc
.tcl,.die,.rehash. Le risque n'est pas moindre, il est déplacé : qui l'atteint a l'interpréteur du bot. ⚠ Mais elle n'écoute qu'en127.0.0.1:3333(mesuré au socket), etrequire-p 1plus unPASS2en place exigent le mot de passe. Le hostmask est donc un facteur, pas le seul — « les pleins pouvoirs avec un ident deviné » allait trop loin.3. ⚠⚠ EN REVANCHE, C'EST LE CODE QUI FABRIQUAIT LE MASQUE OUVERT, et il le refera chez chaque tiers.
::soc::reg::on_passconstruisait*!*@<domaine>à partir de l'hôte de l'enrôleur. Ici le domaine était celui du réseau local de Windows ; chez un installateur quelconque, ce sera celui de son fournisseur d'accès — des milliers d'abonnés reconnus comme owner du bot.⚠ Et le chemin est INVISIBLE sur une installation en service : il ne s'exécute qu'au premier enrôlement, tant qu'aucun owner n'existe. Une mesure faite ici aurait conclu « ce code ne tourne plus » — c'est le point 5 des règles, encore : où vit la chose que je viens de corriger ?
✅ CORRIGÉ (4.19.38). Le masque exige désormais l'ident :
*!*<ident>@<domaine>, le*de tête absorbant le tilde qu'ajoute un serveur sans identd. ⚠ Un hôte inexploitable ne donne aucun masque et le dit — mieux vaut un owner qu'Eggdrop ne reconnaît pas, il lui reste son mot de passe, qu'un masque ouvert posé « pour que ça marche ».Banc neuf :
IriX/tests/test-register.tcl— 24 constats, il exécuteon_passsur un faux Eggdrop, et quatre mutations rougissent. ⚠irix-register.tcln'avait aucun banc, alors qu'il est le seul écrivain du userfile : c'est lui qui crée le handle, pose+nmpet écrit le masque.⚠⚠ Un constat de ce banc a d'abord été écrit FAUX :
string match "*!*@*"pour dire « ce masque est ouvert » — or ce motif absorbe aussi*!*servus@…. Rouge sur du code juste ; écrit dans l'autre sens, il aurait laissé passer le défaut. Le constat lit maintenant la part ident.✅ ET LE MASQUE DE PRODUCTION EST RETIRÉ — constaté le 21/08/2026 à 17:38. Fait à froid, parce qu'Eggdrop garde son userfile en mémoire et le réécrit à l'arrêt : une édition à chaud aurait été écrasée au premier
save. Arrêt, retrait de la ligne, relance — le compteSerVuSne porte plus que son masque à ident et ses deux-telnet!; le fichier est resté en 600.⚠ La restauration était posée dans un
trap EXITAVANT le geste, et le userfile copié de côté. Une coupure oubliée coûte plus cher que le défaut qu'on venait corriger.Le lien de services est remonté seul, et c'est croisé sur deux sources :
ACTIF — présence confirmée côté serveur(WHOIS sourcé,318) à 17:38:50, SID003— celui qu'exige le bloclink, donc le chemin du point 37 a bien été repris —, 5 services en ligne, et l'op obtenu sur#!Bots!et#!Services!. ⚠ LeNEEDOP … tentative 1/3de 17:38:33 n'est pas une panne : c'est la montée normale, 17 s avant que le lien soit en état d'émettre.⏪ CE QUI SUIT EST L'ÉTAT D'AVANT LA FERMETURE — PÉRIMÉ, gardé pour la trace. Le critère décrit ci-dessous a été constaté le 23/08/2026 à 20:58:20 — voir l'encadré ✅ en tête de point. Le premier enrôlement a eu lieu sur l'installation neuve : le masque
*!*servus@127.0.0.1est écrit, avec les deux lignes de journal qu'exigeait le critère. ⚠ Ne pas le relire comme du travail à faire. Un encadré « Pour fermer » périmé sous un titre ✅ est exactement ce qui fait rouvrir un point déjà clos, ou croire ouvert ce qui ne l'est plus.
🟡 CE QUI RESTE — un seul constat, et il ne peut pas se prendre ici : voir le masque à ident réellement posé demande un premier enrôlement, donc une installation neuve. C'est le même trou que le point 26. Le banc l'exécute ; la production, elle, ne repassera jamais par ce chemin.
⚠ Ce point est affiché sur le site public. Le masque de production y était écrit en toutes lettres du 20 au 21/08 ; il ne l'est plus ici, mais l'historique du dépôt le porte — et il ne vaut plus rien depuis 17:38.
⚠⚠ ET UNE PISTE À NE PAS ROUVRIR : « adosser les grades à OverNick » ne s'applique pas ici. Les grades applicatifs le sont déjà — c'est le sens du point 1 ci-dessus. Ce qui reste sur hostmask est la partyline, gardée par Eggdrop lui-même, dans son binaire : aucun service d'identification ne peut s'y substituer sans réécrire Eggdrop. Le durcissement possible est celui qui a été fait — exiger l'ident — plus l'écoute locale déjà en place et le mot de passe.
Ce qui avait été constaté le 20/08, et qui reste juste
Trouvé le 20/08/2026 en instruisant un kick, et le kick n'était que le symptôme.
Le symptôme. SerVuS, connecté par OverScript et bel et bien passé IRCop (MODE 10AAAAAAK :+osw à 10:14:21), tape /mode #!Overtchat! +o SerVuS. Le serveur accepte. Une seconde plus tard, IriX le kicke : « Abus d'une désynchronisation ». La protection native d'Eggdrop ne regarde qu'une chose — « ce nick a-t-il l'op dans MA liste ? » — et elle ne s'applique pas aux utilisateurs qu'Eggdrop reconnaît. Or il ne le reconnaissait pas : le userfile portait *!Tgl0be@…, un ident supprimé depuis, quand OverScript se connecte en servus. Corrigé le jour même, à froid, en 2 secondes de coupure.
⚠⚠ Le fond, qui reste ouvert. Ce compte porte les flags jlmnoptx — n owner, o global op : les pleins pouvoirs sur le bot. Et ce qui les accorde est un hostmask :
| Masque | Ce qu'il exige réellement |
|---|---|
*!servus@<cloak de l'IP publique> | un ident servus et une connexion portant ce cloak |
*!*@Clk-…mshome.net | n'importe quel ident, depuis ce host |
⚠ Le premier cloak est celui de l'IP publique de la Freebox. Ce n'est pas un secret et ce n'est pas personnel : toute connexion venue de la maison le porte, y compris celle d'un visiteur. Ce masque ne protège donc que par l'ident — une chaîne que l'on choisit soi-même à la connexion. Le second ne protège même pas par ça.
⚠ Ce n'est pas une faille exploitable depuis Internet : il faut venir par ce chemin. Ce qui est en cause, c'est que le niveau de garde ne correspond pas à ce qui est gardé.
⚠⚠ La suite a montré que ce paragraphe sur-disait — ces flags n'ouvrent pas « les pleins pouvoirs sur le bot » mais la partyline, qui exige en plus le mot de passe et n'écoute qu'en local. Voir le bloc de mesure en tête du point.
Ce qu'on croyait devoir faire pour fermer — ⚠ et c'est une piste sans issue, mesurée le 21/08 : adosser à OverNick. Les grades applicatifs y sont déjà adossés ; ce qui reste sur hostmask est gardé par Eggdrop dans son binaire, hors d'atteinte d'un service d'identification.
📌 Lien avec le reste : c'est la face « userfile » des deux identités d'IriX — les grades en base d'un côté, les hostmasks Eggdrop de l'autre. Les deux ont divergé sans qu'aucun banc ne le voie, et c'est un utilisateur kické qui l'a signalé.
41. ✅ L'installateur ne posait AUCUNE unité systemd — CONSTATÉ FERMÉ le 20/08/2026
⚠⚠ CE CORRECTIF NE PRODUIT RIEN CHEZ UN TIERS — mesuré le 23/08/2026 sur une Ubuntu 24.04 neuve. Poser les unités suppose qu'elles soient là : Config/systemd/ n'arrive dans aucune installation tierce — ni par l'installateur, ni par le site (404), ni par l'archive /dl/site, ni par un clone, le dépôt étant privé. L'écran y annonce « Unités livrées 0 ». Le constat du 20/08 a été pris sur la machine qui possède le dépôt, où la question ne se pose pas.
⚠ C'est le motif le plus coûteux du projet : corrigé là où l'on regarde, pas là où la chose vit — et un banc restait vert. Les quatre mesures sont au point 43.
Constaté en production le 20/08/2026 à 14:16. SerVuS a lancé la pose : 14/14 unités posées, 0 écart avec le dépôt. La ligne
# PREREQUIS:est arrivée sur les huit.service, les chemins et le compte de service sont substitués (User=servus,WorkingDirectory=/home/servus/Service-Overtchat), et les six timers tournent —operstoutes les 30 s,veilletoutes les 2 min,backupà 04:00. La veille a repassé cinq fois après la repose : rien n'a été perturbé.⚠⚠ ET LA PREMIÈRE TENTATIVE A ÉCHOUÉ SANS LE DIRE — c'était un message de MOI. Le script d'appoint affichait « ✔ 14 unité(s) posée(s) » même en mode
--blanc: le compteur venait de la fonction de l'installateur, et le talon « à blanc » lui répondait « posé » sans rien écrire. Ce sont les dates des fichiers qui l'ont démasqué —/etcdatait encore de 08:49. Un message vrai dans son contexte peut mentir hors de lui, et c'est un piège de plus à mettre au compte des constats menteurs. Corrigé par un bandeau explicite.📌 Ce qui reste n'appartient plus à ce point : les quatre familles de fichiers système hors systemd — sudoers, logrotate, polkit, vhosts Apache — que le désinstalleur sait retirer et que l'installateur ne pose toujours pas. C'est le point 43.
L'état d'avant, gardé parce qu'il explique la décision
✅ La pose est écrite et éprouvée, le même jour. SerVuS a tranché : poser les quatorze, n'activer que ce dont le prérequis est vérifiable sur place. C'est fait —
_sysd_poser,_sysd_activeret l'écran_panel_systemd(étape 6 de l'assistant), plus la convention# PREREQUIS:portée par chaque.service.⚠ Aucune liste centrale n'a été écrite, et c'était la difficulté du point : une table « unité → condition » se périmerait au premier ajout. La liste est dérivée de
Config/systemd/, et la condition vit avec l'unité — le motif de# RATTRAPAGE:, transposé.⚠ L'écran est RÉ-EXÉCUTABLE, et ce n'est pas un confort : la mise à jour n'atteint jamais
/etc/systemd/system/, donc sans lui la première pose serait la dernière.🟡 Ce qui manque pour fermer, et c'est un geste que je ne peux pas faire : lancer la pose pour de vrai, sur une machine où
/etc/systemd/system/est écrivable.Bootstrap/tests/test-systemd-pose.shexécute les fonctions sur un faux/etc— 23 constats, six mutations qui rougissent — mais un bac à sable n'est pas une installation.⚠⚠ Et la passe de mutation a trouvé un trou dans ce banc-là aussi. Retirer la substitution des chemins hors racine le laissait VERT : toutes mes unités d'essai avaient leurs chemins sous la racine du projet, si bien que je mesurais une substitution qui se faisait déjà toute seule. C'est exactement le piège d'
overop@.service, qui porte/home/servus/IriX/eggdrop. Une unité d'essai portant un tel chemin a été ajoutée ; la mutation rougit désormais.Reste aussi, et ce n'est pas couvert : les quatre familles de fichiers système hors systemd (sudoers, logrotate, polkit, vhosts Apache), toujours désinstallables et jamais posées.
L'état d'origine, gardé parce qu'il explique la décision
Trouvé le 20/08/2026 en installant la veille du point 40 : il a fallu écrire la commande à la main, et c'est en cherchant pourquoi que le trou est apparu. Il est bien plus large que la veille.
Mesuré, pas déduit :
| Mesure | Résultat |
|---|---|
ls Config/systemd/ \| wc -l | 14 fichiers d'unités |
grep -n systemctl Bootstrap/overtchat-setup.sh | 2 occurrences, toutes deux sur postfix (l. 9969-9970) |
grep -n 'Config/systemd' Bootstrap/overtchat-setup.sh | 1 seule, l. 8584 — et c'est une exclusion de mise à jour |
Qui connaît /etc/systemd/system ? | Tools/desinstaller.sh — et lui seul |
⚠⚠ On sait donc DÉSINSTALLER des unités qu'on ne sait pas INSTALLER. Et le désinstalleur, lui, est irréprochable : il dérive son inventaire de Config/systemd/ au lieu de l'énumérer (Tools/desinstaller.sh:161-166), avec le commentaire qui dit pourquoi — « une liste écrite à la main oublie ce qu'on ajoute ensuite ». C'est exactement le geste qui manque en face.
⚠ C'est la règle « où vit la chose » dans sa forme la plus pure : ici tout marche, parce que les unités ont été posées à la main, une par une, au fil des mois. Sur une installation neuve, le dépôt porte quatorze unités et /etc/systemd/system/ n'en reçoit aucune — l'état exact de cette machine à 08:48 le 20/08. Les sauvegardes ne tourneraient pas, les opérateurs du panel ne s'appliqueraient pas, les robots ne se poseraient pas, et la veille ne veillerait pas.
⚠ Et rien ne le signale. La campagne complète est au vert — 90 bancs, 4409 constats — parce qu'aucun banc ne compare Config/systemd/ à ce que l'installateur pose. C'est le motif des quatre défauts du 16/08, et le quatrième banc vert qui ne garde rien.
⚠⚠ Et en l'instruisant, le trou s'est révélé plus large sur trois points — chacun mesuré
a) Ce ne sont pas que les unités : QUATRE familles de fichiers système sont dans le même cas. Tools/desinstaller.sh:175-181 sait retirer /etc/sudoers.d/overtchat-services, /etc/logrotate.d/overtchat, /etc/polkit-1/rules.d/10-overtchat-irix.rules et les vhosts /etc/apache2/sites-available/irix-*.conf. L'installateur n'en pose aucun. Or la veille du point 40 ne peut relancer IriX que grâce à la ligne sudoers — sur une installation neuve, elle constaterait sans jamais pouvoir guérir.
b) ⚠⚠ UNE UNITÉ CORRIGÉE N'ATTEINT AUCUNE INSTALLATION EXISTANTE — PAR AUCUN CHEMIN. Mesuré : WATCHED_EXTENSIONS=( "sh" "json" "conf" "tcl" ) (Bootstrap/overtchat-setup.sh:265) — ni .service ni .timer. Le scan de mise à jour ne les ramasse donc jamais, et l'exclusion de la ligne 8584 est une ceinture sur une bretelle qui n'existe pas. Conséquence : Config/systemd/* n'est même pas téléchargé par la mise à jour, et de toute façon toute la machinerie opère sur $BASE_DIR, jamais sur /etc. La première pose est donc la dernière. Cela impose, quelle que soit l'option retenue, un point d'entrée ré-exécutable — sans lui, une correction d'unité est indiffusable.
c) Trois unités citées par le désinstalleur n'existent NULLE PART au dépôt. Vérifié par find : irix.service, overtchat-web.service et unrealircd.service sont introuvables ; seul overtchatd.service existe, et hors Config/systemd/ (Servers/overtchat-hybrid/contrib/). Une pose « dérivée de Config/systemd/ » ne les couvrira donc pas, et le commentaire desinstaller.sh:168-169 — « sont posées par l'installateur » — restera faux.
⏪ CE QUI SUIT EST L'ÉTAT D'AVANT LA FERMETURE — PÉRIMÉ, gardé pour la trace. Le critère décrit ci-dessous a été tenu le 20/08/2026 — voir le constat en tête de point. La décision A/B posée ici a été tranchée par SerVuS, et le code livré : 14/14 unités posées, constatées en production à 14:16. ⚠ Ne pas le relire comme du travail à faire. Un encadré « Pour fermer » périmé sous un titre ✅ est exactement ce qui fait rouvrir un point déjà clos.
Pour fermer — ⚠ il y a une décision avant le code, et elle tient en une phrase : sur une installation neuve, quel doit être l'état de /etc/systemd/system/ à la sortie de l'installateur ?
| A — poser les 14, n'en activer aucune | B — poser et activer selon les composants installés | |
|---|---|---|
| Geste | un seul, dérivé du dossier | conditionné par un prédicat par unité |
| Ajout futur d'une unité | couvert automatiquement | à ajouter à la main dans la table |
Risque de failed | nul, rien ne démarre | nul, mais la table doit rester juste |
| Après l'installation | l'exploitant active lui-même | tout tourne : sauvegardes, opérateurs, veille |
| Défaut du choix | fermé à moitié | une liste écrite à la main, ce que desinstaller.sh:159 interdit précisément |
⚠ Dans les deux cas, sans discussion : la liste est dérivée de Config/systemd/, jamais énumérée ; les chemins et le compte sont substitués sur le modèle déjà livré et éprouvé de Servers/overtchat-hybrid/contrib/install-service.sh:141-145 (⚠ qui vise ^User=servus$ nommément, et non un s/servus/…/g global qui réécrirait commentaires et chemins déjà traités) ; overop@.service est traité à part — c'est un template, jamais enable sans instance ; et l'activation ne vise que le .timer pour les sept unités oneshot, dont les [Install] les feraient sinon tourner au démarrage en plus du timer.
⚠ Le sed livré ne couvre pas tout : overop@.service porte /home/servus/overop-%i et /home/servus/IriX/eggdrop, deux chemins sans Service-Overtchat dedans.
⚠ Et adosser un banc qui compare les deux sens (dépôt → posé, posé → dépôt), sans quoi ce point se rouvrira seul au prochain ajout — c'est très exactement ce qui vient de se produire avec la veille.
40. ✅ L'alerte du bot se taisait EXACTEMENT quand il fallait qu'elle parle — CONSTATÉ FERMÉ le 20/08/2026
Les trois gestes sont constatés, chacun sur une source indépendante. Ouvert le 19/08, fermé le 20/08 : la veille extérieure est installée et relance pour de vrai ; le rangement automatique fait qu'une panne qui revient reparle ; et le filet horaire poste ce que le cycle ordinaire n'a pas posté. Le détail de chaque constat est plus bas, à sa place.
⚠⚠ Ce que ce point aura appris, et qui vaut au-delà de lui : COUPER LE SERVICE DEUX FOIS. Le premier essai a tout validé — détection, base, courriel accepté par le relais, relance. Le second, cinq minutes plus tard, n'a rien produit : c'est lui qui a trouvé le défaut, et trois bancs verts ne le voyaient pas. Le premier essai éprouve le chemin ; le second éprouve le retour à l'état initial, qui est la moitié qu'on n'écrit jamais.
Sorti du point 38, dont il était le deuxième reliquat. La cause des dix millions de minuteurs est corrigée et constatée ; ce défaut-ci ne l'est pas, et il ne dépend pas d'elle.
⚠⚠ ::soc::alerte::cycle_courriels est armé par un utimer. Quand les minuteurs manquent — saturation, ou n'importe quelle autre cause future —, la ligne d'alerte part bien au journal et en base, mais le courriel ne part jamais. C'est ce qui a rendu les pannes des 18 et 19/08 totalement muettes : cinq heures, puis trois heures et demie, sans que personne soit prévenu.
⚠ Ce n'est pas réparable de l'intérieur. Une alerte portée par le mécanisme qu'elle doit surveiller partage sa maladie. Le seul porteur interne qui survivrait est un bind time (déclenché par l'horloge, pas par la file des minuteurs) — et tout nouveau bind time doit déclarer sa ligne # RATTRAPAGE:, contrôlée par IriX/tests/test-bindtime.tcl.
Ce qui EXISTE déjà, et qu'il ne faut pas réécrire (4.19.18) :
Tools/veille-irix.php— une sonde qui vit hors du bot, lancée par systemd. Elle ne lit que des signaux qu'un bot malade ne peut pas simuler : le contenu de la queue du journal, la mémoire du processus, un journal figé, et un journal qui enfle au-delà du plafond. ⚠ Les quatre menteurs mesurés pendant les pannes sont écartés, et le fichier dit pourquoi :systemctl is-active(ditrunningsur un bot étouffé),connecteden base (est restéoui35 min après la mort du lien), la taille du journal (il grossissait de 1,5 Go… d'erreurs), le socket en écoute (survit au gel).Config/systemd/overtchat-veille.{service,timer}— sonde toutes les 2 min, relance au plus une fois par heure. ⚠SuccessExitStatus=0 1: une anomalie détectée n'est pas un échec de la veille, c'est son travail.- Éprouvée sur quatre cas (sain, saturation, journal figé, processus absent).
✅ LA JOURNÉE DU 20/08/2026 — les trois gestes, et ce que l'essai a trouvé
1. ✅ INSTALLÉE — constaté le 20/08/2026 à 08:49:33. enabled + active, lien timers.target.wants en place, premier passage à 08:49:33 (ExecMainStatus=0 : IriX allait bien). ⚠ Le sed de l'en-tête n'a pas été appliqué, et c'est correct : la racine de prod est littéralement /home/servus/Service-Overtchat. ⚠ overtchat-veille.service reste disabled volontairement, comme motd et backup — c'est le timer qui déclenche.
2. ✅ ELLE ALERTE POUR DE VRAI — constaté deux fois, dont une sans aucune main humaine.
| Constat | Preuve, par une source indépendante |
|---|---|
| détection | CRIT — IriX ne tourne plus, code de sortie 1 |
| écriture en base | ligne veille/absent, mail_at posé |
| courriel réellement remis | journal Postfix : status=sent, accepté par smtp.free.fr |
| relance automatique | sudo[…]: COMMAND=/usr/bin/systemctl restart irix.service — chemin jamais exercé jusque-là |
| systemd traite 1 comme un succès | Finished, et non Failed — SuccessExitStatus=0 1 à l'œuvre |
sudo sans terminal | trace PAM à 08:57:34 — le doute sur Defaults use_pty est levé |
⚠ Délai réel de bout en bout : 81 s entre la coupure et la relance. Sur une saturation qui met 77 à 102 min à s'installer, c'est très largement dans les clous.
⚠⚠ ET L'ESSAI A TROUVÉ UN DÉFAUT QUE TROIS BANCS VERTS NE VOYAIENT PAS. En coupant IriX deux fois au lieu d'une : la première panne a produit un courriel, la seconde rien du tout. La ligne restait ouverte avec son mail_at posé, et les requêtes d'envoi ne retiennent que mail_at IS NULL — une cause devenait donc muette pour toujours, jusqu'à un acquittement humain. Les pannes des 18 et 19/08 étaient précisément des pannes répétées : seule la première aurait parlé. Corrigé en 4.19.27 (alerte_ranger_disparues(), adossée à alerte_acquitter()), et constaté en production à 09:15→09:21 : deux pannes, deux courriels, deux rangements automatiques par=veille. Second écart trouvé au passage : le code de sortie 2 n'avait jamais été rendu, alors que l'en-tête du fichier et le .service l'annonçaient tous les deux.
📌 La leçon, et elle vaut au-delà de ce point : couper le service DEUX fois au lieu d'une. Le premier essai valide le chemin ; c'est le second qui trouve ce qui ne se réarme pas.
3. Le sort de l'alerte interne — tranché et livré en 4.19.28. Voir plus bas.
Ce qu'il fallait pour fermer :
- ⚠⚠ L'INSTALLER — c'est un geste de l'exploitant. Écrire dans
/etc/systemd/system/exige un mot de passe ;sudo -nne couvre questart/stop/restartsur trois unités. La procédure est en tête du.service. SerVuS a donné son accord le 19/08/2026 (avec relance automatique) ; le geste a été fait le 20/08 à 08:49:33. - Constater qu'elle alerte pour de vrai — provoquer l'anomalie et voir le courriel arriver. ⚠ Ne pas se contenter du code de sortie de
Tools/alerte-courriel.php: il rend 0 aussi quand l'alerte était déjà envoyée ou déjà rangée. ⚠⚠ CETTE PHRASE ÉTAIT FAUSSE, et elle a failli fausser un constat. Elle annonçait un limiteur à « 2 courriels par heure et par adresse » qui étranglerait une sonde bavarde en silence. Il n'y en a aucun sur ce chemin. Mesuré le 20/08/2026 :contact_rate_block()vit dansWeb/inc/contact.phpet n'est appelée que par le formulaire de contact public ;smtp_send()ne porte aucun plafond, etTools/alerte-courriel.phpl'appelle directement. Le seul bornage des alertes est leLIMIT 3par passage et le regroupement d'une cause ouverte en une seule ligne. ⚠ Sans cette vérification, un essai qui n'aurait rien reçu aurait été mis sur le compte du limiteur, et le vrai défaut serait resté caché — c'est précisément ce qui a failli arriver le 20/08. - Décider du sort de l'alerte interne. Tant qu'elle reste sous
utimer, elle continuera de se taire dans le seul cas qui compte. Soit on la porte sur unbind time, soit on assume que la veille extérieure est le seul chemin d'alerte fiable — et alors il faut l'écrire, parce que le code laisse aujourd'hui croire le contraire.
✅ TRANCHÉ PAR SerVuS LE 20/08/2026 : les deux à la fois, et c'est livré en 4.19.28. La veille extérieure est écrite comme le chemin fiable, et un filet horaire est posé côté bot — ::soc::alerte::rattrapage_courriels, liée par un bind time à "05 * * * *" dans IriX-binds.tcl.
⚠ Les deux porteurs couvrent deux pannes qui n'ont rien à voir, d'où le refus de choisir : un gel de Windows DÉCALE un utimer (rien n'est perdu) mais PERD un bind time — c'est le point 15, et c'est pourquoi le cycle principal reste sur utimer ; une saturation de la file des minuteurs, elle, tue le utimer pour de bon, et seule l'horloge y survit.
⚠ Le filet ne pose aucun minuteur et ne se réarme pas : un filet qui dépendrait de ce qu'il rattrape ne rattraperait rien. Il est recevable en forme « RATTRAPAGE: aucun — ASSUMÉ » parce qu'il est idempotent — il vide une file dont le critère est mail_at IS NULL, donc un tour manqué n'est pas un travail perdu.
⚠ Il DIT au journal quand il a réellement rattrapé quelque chose, et se tait sinon. Un filet muet serait invérifiable — le défaut même de ce point, un étage plus haut.
⚠⚠ IriX/tests/test-alerte.tcl:326 EXIGEAIT l'absence de tout bind time : le banc interdisait littéralement ce correctif. Il a été retourné, et pas seulement supprimé — le bind vit dans IriX-binds.tcl, que ce banc ne lisait pas. Le retirer aurait laissé le banc vert sans plus rien garder ; poser le bind ailleurs l'aurait laissé vert pour une mauvaise raison. Il lit désormais les deux fichiers et ancre la procédure ici, sa liaison là-bas.
⚠ Le constat « le filet ne pose aucun minuteur » cherche un appel, pas un mot — début de ligne ou juste après une accolade, ce qui couvre le catch { utimer … } par lequel on le contournerait. Éprouvé dans les deux sens.
⚠ test-bindtime.tcl a rougi tout seul en cours de route, sur un bloc de commentaire trop long : la convention exige la ligne # RATTRAPAGE: dans les 15 lignes au-dessus du bind, contiguë. C'est une garde qui mord pour de vrai.
⚠ Le bind est posé AVANT que la procédure existe — IriX-binds.tcl est sourcé ligne 39, irix-alerte.tcl ligne 670. Sans conséquence (Eggdrop résout au déclenchement), et le démarrage de 09:38:32 le confirme sans une erreur ; c'est le motif des deux autres bind time. ⚠ Mais un renommage ne se verrait qu'une fois par heure, dans le journal d'Eggdrop seul — c'est ce que garde la mutation « proc renommée » du banc.
✅ LE BIND EST CONSTATÉ EN SERVICE — 20/08/2026 à 10:05:00, dans journalctl -u irix :
10:05:00 triggering bind ::soc::alerte::rattrapage_courriels
10:05:00 triggered bind ::soc::alerte::rattrapage_courriels, user 0.103ms sys 0.109ms
Trois choses d'un coup, et c'était le maillon le plus risqué : le bind est enregistré, il est déclenché par l'horloge, et la procédure s'exécute sans erreur — donc sa signature à cinq arguments est bonne et le bind ne pointe pas dans le vide.
✅ ET IL POSTE — CONSTATÉ le 20/08/2026 à 11:05:00. Au premier essai, à 10:05, la file était vide : le filet n'avait rien à faire et s'est tu, comme prévu. Repris à 11:05 avec la fenêtre calculée juste :
11:04:30 alerte id=17 posée, mail_at=NULL
11:05:00 triggering bind ::soc::alerte::rattrapage_courriels
11:05:00 SOC ALERTE: filet horaire — 1 courriel(s) rattrapé(s), le cycle ordinaire n'avait pas suivi
11:05:00 [INFO] alerte : courriel #17 — Envoyée à service.overtchat@free.fr
11:05:01 postfix: status=sent (250 2.0.0 Ok: queued as B9476B00573)
⚠ La preuve est DIRECTE, et c'est pour cela que le filet parle : la ligne « filet horaire — N courriel(s) rattrapé(s) » n'est écrite que par rattrapage_courriels, par personne d'autre. Un filet muet aurait laissé la question ouverte — on aurait vu un courriel partir sans pouvoir dire lequel des deux chemins l'avait envoyé.
⚠ Et la course a été rendue déterministe : le cycle ordinaire passe à :55, le filet à hh:05:00, l'alerte a été posée à 11:04:30 — entre les deux.
⚠⚠ ET LA RAISON VAUT PLUS QUE LE CONSTAT MANQUÉ. La fenêtre d'essai avait été calculée fausse de 22 secondes. Le cycle ordinaire ne passe pas là où je l'avais déduit : demarrer_courriels est appelée dans un after 15000 après db_init, pas au démarrage du bot — le pas tombe donc à :55, et 09:38:55 + 5×300 s = 10:03:55 exactement. L'alerte d'essai, posée à 10:03:50, a été prise par le cycle ordinaire cinq secondes plus tard.
⚠⚠ ET LA TRACE ÉTAIT DANS LE TROISIÈME JOURNAL. IriX/runtime/IriX.log ne montrait rien ; la ligne « alerte : courriel #16 — Envoyée à … » vit dans IriX/cmds/IriX-sql.log. En ne regardant que le premier, on conclut « aucun envoi » et on accuse le mauvais mécanisme. C'est la règle des trois journaux, prise en défaut le jour même où on l'applique.
La méthode, pour refaire l'essai : le cycle ordinaire passe à :55 ; le filet tire à hh:05:00. Poser l'alerte d'essai entre hh:03:56 et hh:04:59 — le filet la prend, et le cycle ordinaire ne repasse que quatre minutes après lui. ⚠ Sans ce calcul, on constate le cycle ordinaire en croyant constater le filet : un vert authentique, pour la mauvaise raison.
39. ✅ La référence d'intégrité vivait dans /tmp — CONSTATÉ FERMÉ le 20/08/2026
Constaté en production le 20/08/2026 à 13:46, l'installateur lancé par SerVuS :
Runtime/state.jsoncréé, en600, portant.install=true, la langue et le nom du projet./tmp/.service-overtchat.jsonn'existe plus. Le fichier est ignoré par git (.gitignore:55), l'arbre du clone de production est propre, et les cinq services tournent.⚠ Les six empreintes sont à
AUCUNE, et c'est le comportement voulu : le fichier vient de naître, et la vérification refuse désormais au lieu d'adopter l'état en place. Les poser est un geste délibéré — entrée 13 du menu, grade 80, confirmation en toutes lettres. C'est le pendant assumé du refus, ajouté en 4.19.31 parce que la migration a rendu le manque concret en une minute : plus rien n'était vérifiable, et rien ne permettait de repartir.🟢 REMESURÉ LE 21/08/2026, et la mesure corrige un détail qui compte : ce ne sont plus six empreintes absentes mais cinq —
services,servers,access,eggdrop,irix. La sixième,overop, s'est reposée toute seule le 20/08 à 14:57, quandConfig/overop.jsona été réécrit :_sha256_storeest appelée à chaque écriture de configuration.⚠ Donc « rien ne les repose tout seul » est faux, et le nuancer évite une fausse alerte : une empreinte reparaît dès qu'on règle le fichier correspondant depuis le panel. Le geste de l'entrée 13 sert à poser les cinq autres d'un coup, sans avoir à toucher à cinq réglages pour rien.
Le reste du constat tient : fichier en 600, gitignoré (
.gitignore:55),/tmpne porte plus ni.service-overtchat.jsonni son jumeau, et la clébootstrap— celle qui n'a aucun appelant — est bien restée ànull, sans que personne l'ait ré-enregistrée au passage.
🟡 CORRIGÉ LE 20/08/2026 — reste à le CONSTATER sur une installation
INIT_BASEvaut désormais$BASE_DIR/Runtime/state.json, en600, déclaré sousBASE_DIR(il l'était 110 lignes trop haut, là où la variable n'existe pas encore).mkdir -pposé sur les deux chemins de démarrage — ⚠ dont le chemin CLI/cron, qui n'appelle jamais_create_folders: c'est le piège le plus discret, la dépendance étant circulaire puisque_create_foldersdérive ses chemins de ce fichier même.⚠⚠ LA MESURE A RETOURNÉ LE POINT. Ce fichier n'est pas un fichier d'empreintes : il porte
.install— la mémoire que l'installation a eu lieu —,.folderset ses 18 chemins, la langue, le nom du projet, les 32 modules Tcl à déployer. Sa perte ne cassait donc pas seulement l'intégrité : elle faisait croire à l'installateur qu'il n'avait jamais tourné. La « voie étroite » envisagée plus bas — ne sortir que les six clés — n'avait donc pas de sens : elle aurait créé deux sources de vérité pour un même état.⚠
Runtime/et nonConfig/: le cheminRuntime/state.jsonétait déjà réservé dans_upd_exclupour un fichier qui n'existait nulle part. Une des deux listes était faite d'avance ;.gitignorea été complété dans le même commit.Les trois défauts logiques ont été corrigés DANS LE MÊME GESTE, et il le fallait : un fichier persistant les aurait figés au lieu de les révéler — le mécanisme aurait paru sain parce qu'il ne se réinitialisait plus. Une référence absente est refusée ; le code de retour n'est plus avalé (
2corrompu,1sans référence,0sinon) ; la cléoveropest enfin vérifiée.Banc neuf :
Bootstrap/tests/test-empreintes.sh— 17 constats, il exécute les trois fonctions, et quatre mutations rougissent. ⚠ Ne pas le confondre avectest-integrite.sh, qui éprouve le manifeste distribué : la confusion a déjà trompé une lecture deSECURITY.md.⚠ Trou dormant fermé au passage :
Config/overop.jsonn'était dans aucune des deux listes.Pour fermer : lancer l'installateur sur cette machine et constater que
Runtime/state.jsonest créé, en600, et que l'option 9 dénonce un fichier altéré au lieu de l'adopter.
L'état d'origine, gardé parce qu'il explique la correction
Constaté le 19/08/2026 en remesurant SECURITY.md. Le panel d'administration propose « Vérifier l'intégrité SHA256 » (option 9). Il compare les fichiers de configuration à des empreintes relevées à l'installation. Ces empreintes vivent dans /tmp.
⚠⚠ CE N'EST PAS UN RISQUE THÉORIQUE : LE FICHIER EST ABSENT EN CE MOMENT.
| Mesure du 19/08/2026 | Résultat |
|---|---|
INIT_BASE | /tmp/.service-overtchat.json |
| Le fichier existe-t-il ? | Non — ni lui, ni son jumeau .default.json |
Nature de /tmp | tmpfs — il vit en mémoire, tout arrêt de la VM le vide |
systemd-tmpfiles-clean.timer | actif, a tiré les 18/08 et 19/08 ; purge à 10 jours |
| Empreintes perdues | 6 (access, eggdrop, services, servers, irix, overop) |
Deux effaceurs cumulés, tous deux mesurés. Sous WSL la fenêtre est plus COURTE qu'ailleurs : la VM redémarre à chaque fermeture de session Windows et sur arrêt d'inactivité.
⚠⚠ LE PIRE N'EST PAS LA PERTE, C'EST CE QUE FAIT LA PASSE SUIVANTE. Quand la référence manque, _sha256_verify affiche « Pas de hash référence — enregistrement… » et enregistre l'état en place, quel qu'il soit. Elle adopte donc une éventuelle altération au lieu de la dénoncer, et le passage d'après la déclarera saine. Une détection qui ne détecte pas est pire que pas de détection : elle rassure.
⚠ Et cela ne remonte à personne : la fonction rend 1 dans ce cas, mais _integrity_check_all avale le code par || true. Rien ne s'accumule, rien n'alerte, rien ne distingue à l'œil un ré-enregistrement en masse d'un contrôle réussi.
Trois défauts voisins, mesurés au passage — à traiter ici ou à acter à part :
- La clé
overopest écrite et morte. Trois fonctions enregistrent consciencieusement l'empreinte deConfig/overop.json; rien ne la relit jamais._integrity_check_allne vérifie que cinq clés. - Le panel lui-même n'est jamais vérifié. L'option 9 contrôle des fichiers de configuration, jamais le code qui les contrôle. La clé par défaut
bootstrapn'a aucun appelant. - ⚠⚠ AUCUN BANC ne couvre
_sha256_store,_sha256_verifyni_integrity_check_all. ⚠ Ne pas se fier au nom deBootstrap/tests/test-integrite.sh: il éprouve l'AUTRE mécanisme, celui du manifeste distribué. Les deux n'ont rien à voir, et les confondre a déjà trompé la lecture deSECURITY.md.
Ce qu'il faut pour fermer — dans cet ordre :
- Sortir la référence de
/tmp. ⚠ Décision à prendre :Config/est l'emplacement cohérent (voisin d'update_state.json, gitignoré, exclu de la mise à jour, sauvegardé) ;Runtime/est défendable carRuntime/state.jsonest déjà prévu dans_upd_exclusans exister. ⚠ Une voie plus étroite existe : ne sortir que les six clés d'empreinte et laisser le reste de la configuration d'exécution où il est — moins de surface, moins de casse. - ⚠⚠ Inscrire le nouveau chemin dans LES DEUX listes —
_upd_excluet.gitignore. Le commentaire de_upd_excluraconte déjà cette régression exacte pourConfig/travaux.json: un fichier d'état non exclu entre dans la liste surveillée, donne un 404 permanent, un rapport rouge en continu et un courriel d'alerte à chaque passage. Côté git, un fichier non suivi que le prochain commit voudrait créer fait échouermerge --ff-onlyet laisse le déploiement en arrière. - Créer le répertoire parent avant la copie dans
main: le gabarit est copié avant_create_folders. Sans cela, premier lancement en échec — une panne de démarrage, pas d'intégrité. - ⚠⚠ NE PAS migrer silencieusement depuis
/tmp. La référence est déjà perdue ; et si l'on en trouvait une, rien ne dirait qu'elle décrit un état sain — elle a pu être ré-enregistrée après un redémarrage. Migrer donnerait à des empreintes suspectes l'apparence d'une référence durable. Ré-enregistrer, et le dire. - Distinguer « référence absente » de « vérification réussie » autrement que par un code de retour qu'un
|| trueavale. - Écrire le banc qui manque : enregistrer, vérifier OK, altérer, vérifier INVALIDE, supprimer la référence, constater que la passe suivante ne fait qu'enregistrer. Ce dernier cas est le défaut de ce point : un banc qui ne l'éprouve pas ne garde rien.
⚠ Effet de bord à assumer : sous BASE_DIR, le fichier entre dans la sauvegarde. Restaurer une archive ancienne restaurera des empreintes anciennes, et toute configuration légitimement modifiée depuis criera « HASH INVALIDE ». C'est un coût réel du déplacement, pas une objection.
38. ✅ IriX fabriquait dix millions de minuteurs et s'étouffait — CONSTATÉ FERMÉ le 19/08/2026 sur 3 h 15 de marche
⚠⚠ LA CAUSE ÉCRITE ICI JUSQU'AU 19/08 ÉTAIT FAUSSE, ET ELLE ENVOYAIT CHERCHER AU MAUVAIS ENDROIT. Ce point accusait le gel de WSL : « le gel empêche les minuteurs déjà posés d'arriver à terme ; ils s'empilent ; au dégel la limite est franchie ». Un gel ne fabrique pas de minuteurs — il les retarde.
Ce que le binaire dit (Eggdrop 1.10.2, lu au désassemblage) : add_timer ne contrôle aucun nombre de minuteurs vivants. Il formate le nom timer<N> avec snprintf(buf, 8, …), soit sept chiffres au plus, à partir d'un compteur global jamais remis à zéro. Au dix-millionième identifiant émis depuis le démarrage, tout utimer est refusé. llength [utimers] peut valoir 9 et l'armement échouer quand même.
Ce que les archives disent — trois épisodes, pas un :
| Épisode | Fenêtre | Volume |
|---|---|---|
| avant le 18/08 09:43 | — | 1,12 Go, 9,84 M erreurs |
| 18/08 11:26 → 16:40 | 102 min après démarrage | 1,56 Go, 13,78 M erreurs |
| 18/08 17:58 → 21:28 | 77 min après le redémarrage « remède » | 1,56 Go, 13,77 M erreurs |
⚠ Le troisième n'avait jamais été vu. Il a commencé 1 h 17 après le redémarrage du 18/08 16:41 et ne s'est arrêté qu'à l'extinction de la machine (TERMINATE SIGNAL), pas par une guérison. ⚠ Et dans les deux épisodes datables, l'échec commence au même identifiant, timer5000228 — deux mesures indépendantes sur le même plafond.
La vraie cause, deux fautes qui se cachaient l'une l'autre, toutes deux dans ::soc::auth (IriX/cmds/IriX-sql.tcl) :
schedule_scanarmait parutimeret tuait parkilltimer. ⚠⚠ Ce sont deux files distinctes dans Eggdrop :tcl_killtimercherche dans la liste à la minute,tcl_killutimerdans celle à la seconde. Un identifiant rendu parutimern'y est jamais trouvé ; l'erreur mourait dans lecatchet le minuteur précédent restait armé.scanreplanifiait après avoir appeléscan_passsouscatch, alors quescan_passreplanifiait déjà sur ses quatre chemins.
→ deux minuteurs posés par tour, aucun retiré : doublement toutes les 60 s.
⚠⚠ ET LE COMMENTAIRE DU CODE AFFIRMAIT LE CONTRAIRE. Il disait « le double réarmement est SANS DANGER, schedule_scan tue le minuteur précédent ». Écrit le 17/08 à 18:04, la veille de la panne. C'est le piège connu du projet — lire le commentaire du correctif au lieu du code — et il a coûté le réseau deux fois.
Corrigé le 19/08 (4.19.17) : les dix killtimer posés sur des utimer sont devenus des killutimer (aucune procédure stop du bot n'arrêtait quoi que ce soit), les quatre réarmements de scan_pass sont retirés — le réarmement appartient à scan, et à lui seul —, et le commentaire menteur est remplacé par le récit des faits. ⚠ Robots/overop/overop.tcl est correct : il apparie timer avec killtimer. Ne pas le « corriger ».
Gardé par IriX/tests/test-minuteurs.tcl, qui dérive les deux règles du code. ⚠ Sa première version ne lisait aucun cycle et affirmait pourtant que tout allait bien : c'est son garde-fou « des cycles ont bien été lus » qui l'a dit. Un détecteur qui ne détecte rien a l'allure d'un projet sain.
✅ LE CONSTAT, PRIS LE 19/08/2026 — 3 h 15 de marche
Veille lancée à 11:41:47, quarante relevés toutes les cinq minutes jusqu'à 15:01.
| Mesure | Résultat |
|---|---|
Too many timers au journal courant | 0, sur toute la durée |
| Mémoire, début → fin | 21 756 → 22 432 Ko, soit +676 Ko en 3 h 15 |
| Mémoire depuis 12:11 | strictement identique — 2 h 45 sans une variation |
| Lien de services | inchangé, même ligne ACTIF du début à la fin |
À comparer à la session malade : +161 Mo en 31 minutes.
⚠ Les deux fenêtres de saturation sont franchies, et largement. Les épisodes des 18 et 19/08 saturaient à 77 et 102 minutes ; la veille a tenu 195 minutes, soit deux fois et demie la première. ⚠ Et la platitude n'est pas un argument à elle seule — la croissance était exponentielle, donc plate au début : c'est la durée qui tranche, pas la pente.
Ce qui a fermé le point, dans l'ordre : la cause corrigée (4.19.17 — killutimer à dix endroits, et le réarmement rendu unique), le plafond de taille des journaux (4.19.19), puis ce constat.
⚠⚠ CE QUI RESTE N'EST PAS DANS CE POINT — il est devenu le point 40. L'alerte du bot passe elle-même par un utimer : elle se tait exactement quand il faudrait qu'elle parle. La veille extérieure qui répond à ce défaut est écrite et éprouvée, mais pas installée. C'est un défaut distinct, qui ne dépend pas de la fuite corrigée ici.
⚠ Remède immédiat, éprouvé deux fois : sudo -n /usr/bin/systemctl restart irix.service — RSS 183 Mo → 21,7 Mo le 19/08, lien actif en une seconde. ⚠⚠ Nommer l'unité avec .service, sans quoi sudo réclame un mot de passe et envoie chercher un problème de droits inexistant.
37. ✅ Après une rupture de lien, IriX choisissait un SID que le serveur refuse — CONSTATÉ FERMÉ le 24/08/2026, echec_burst compris
| ✅ CONSTATÉ FERMÉ | Le 24/08/2026 entre 17:52:45 et 17:55:24, sur la production, avec l'accord de SerVuS. SID 002 posé en base, remise à 003 armée dans un trap avant la moindre écriture. Le lien est rétabli. |
|---|
Les quatre traces du critère, toutes relevées :
| Attendu | Mesuré |
|---|---|
socket_state='error' | oui, dès 17:52:45 |
last_error renseigné | « refus du serveur: SID does not match the configured one » |
| rejeu toutes les 30 s | 17:52:45 · 17:53:15 · 17:53:45 · 17:54:15 · 17:54:45 |
retry_count qui monte | 1 → 3 → 4 → 5 → 6 |
| alerte au troisième | REPETITIONS = 3 : l'alerte lève au 3ᵉ échec, puis le compteur repart |
L'alerte, en base — IriX_alertes id 18 :
cle : burst:Overtchat:refus du serveur ← le format exact du critère
niveau : crit source : uline
sujet : Le lien « Overtchat » n'aboutit pas — le réseau est sans services
compte : 2 ← elle a levé DEUX fois, ~6 échecs
⚠ source='uline' : c'est la première de tout le registre. Les quatorze alertes antérieures venaient de essai, veille, logrot et botnet — cette source-là n'avait jamais servi.
La remise en état, vérifiée : SID=003 | active | connected=oui | retry=0 | last_error vide, « 5 service(s) en ligne » à 17:55:15, et le lien porte de nouveau du trafic (BOTSTAT, PING/PONG).
Et le SERVEUR le dit aussi — deuxième source, indépendante du bot. journalctl -u overtchatd :
17:52:45 → 17:54:45 WARN Lien refusé pour IriX.overtchat.fr : SID 002 annoncé, 003 attendu (×6)
17:55:15 NOTICE Lien établi avec IriX.overtchat.fr (SID 003) [ULine]
17:55:15 INFO Burst : 1 serveurs, 5 clients, 6 salons — Synchronisation terminée
⚠ C'est le serveur qui nomme la valeur attendue. Le bot dit « refus du serveur » sans savoir pourquoi ; seul ce journal-là porte « 003 attendu ». Il n'est pas dans les trois que ce projet croise d'habitude — c'est un cinquième endroit, et il faut y penser pour tout ce qui touche au lien.
⚠⚠ UN PIÈGE ÉVITÉ SANS LE SAVOIR, ET IL FAUT L'ÉCRIRE. Le faux SID doit être de forme valide — 002, 007, 0ZZ. Un SID de forme invalide (ZZZ, vide, NULL) ne serait pas envoyé au serveur : connect_socket appellerait next_free_sid et écraserait la valeur en base, faisant perdre l'original. Et le premier SID libre qu'il choisirait serait justement 002. Qui refait cet essai avec une valeur fantaisiste perd le 003 de la base, et doit aller le rechercher dans overtchat.conf — où c'est le serveur qui l'impose, link IriX.overtchat.fr { sid "003"; … }.
⚠⚠⚠ CE QUE LE CRITÈRE NE DISAIT PAS, ET QU'IL FAUT AJOUTER : L'ESSAI ENVOIE UN VRAI COURRIEL. Il annonçait « coupe les services ~90 s » — c'est vrai, mais incomplet. À 17:56:23, l'alerte a déclenché un courriel réellement expédié :
IriX_mail_log id 24 · dest service.overtchat@free.fr · statut ok
sujet : [Alerte] uline — Le lien « Overtchat » n'aboutit pas — le réseau est sans services
postfix : status=sent, relay=smtp.free.fr[212.27.48.4], 250 2.0.0 Ok
⚠ J'ai acquitté l'alerte seize secondes trop tard. Je croyais l'envoi calé sur le rattrapage horaire (bind time "05 * * * *") : c'est faux, ce balayage n'est qu'un filet, l'envoi normal part bien plus vite. Qui refait cet essai doit acquitter l'alerte AVANT qu'elle ne lève, ou accepter qu'un courriel d'alerte critique parte pour de bon.
Constaté en production le 17/08/2026, en vérifiant le réseau après un gel de WSL. Le réseau est resté sans aucun service pendant 1 h 45, sans qu'une seule alerte parte.
L'enchaînement, lu au journal :
| Heure | Ce qui s'est passé |
|---|---|
| 18:31:21 | le lien tombe — Ping timeout: 97 seconds, socket en broken pipe (le gel de WSL) |
| 18:31:51 | reconnexion : « SID auto-attribué : 002 » |
| 18:31:51 | le serveur refuse : ERROR :SID does not match the configured one |
| 20:10:20 | nouvelle tentative — même SID 002, même refus |
⚠ Le serveur a raison de refuser : son bloc link IriX.overtchat.fr déclare sid "003" (et link services.overtchat.fr déclare 005). C'est IriX qui s'attribue un SID au lieu de reprendre celui que sa configuration impose.
⚠⚠ ET RIEN NE LE SIGNALE. Le refus est propre, côté serveur : pas d'exception, pas de socket cassée, pas d'erreur Tcl. IriX_ulines_sockets porte connected = non et le réseau tourne sans services — OverTchat, OverHub, OverNick, OverBot et OverWeb absents — pendant que tout le reste a l'air normal. C'est une panne totale d'un sous-système, silencieuse.
⚠ Second verrou, qui a empêché la réparation douce : corriger le SID en base n'a pas suffi. socket_state était resté à connecting, et cet état bloque toute nouvelle tentative — le lien ne repart pas tant qu'il n'est pas remis à zéro. C'est le motif exact du verrou topic_running du point 36 : un verrou posé qu'un chemin d'échec ne rend jamais. Il a fallu redémarrer le bot.
Réparé le 17/08 à 20:16 : server_sid remis à 003, redémarrage d'IriX, lien active, cinq services en ligne, reflet reconstruit.
⚠ Ne pas conclure trop vite que le gel de WSL est la cause : il n'a été que le déclencheur. N'importe quelle rupture — redémarrage du serveur, coupure réseau, SQUIT — mène au même état.
🔁 IL S'EST REPRODUIT LE SOIR MÊME — et ce que la seconde fois a appris
17/08/2026, 20:35:14. Deux heures après la première réparation, à l'ouverture de la séance suivante. Même enchaînement, aux mêmes minutes près :
| Heure | Ce qui s'est passé |
|---|---|
| 20:35:14 | le lien tombe — Ping timeout: 90 seconds |
| 20:35:44 | reconnexion : « SID auto-attribué : 002 » |
| 20:35:44 | ERROR :SID does not match the configured one |
| 20:35:44 → 20:38 | plus une seule ligne de journal. Aucune nouvelle tentative |
⚠⚠ CE QUE LA PREMIÈRE ANALYSE AVAIT MANQUÉ, ET QUI CHANGE TOUT : le code ÉCRASE server_sid EN BASE. Deux relevés à trente secondes d'écart le prouvent — 003 à 20:35:0x, 002 à 20:35:44. Ce n'est pas seulement que la valeur configurée est ignorée : elle est détruite. Voilà pourquoi « corriger le SID en base n'a pas suffi » — la correction ne survivait pas à la tentative suivante.
⚠ Et la seconde fois, socket_state valait disconnected, pas connecting. Le remettre à disconnected n'a rien relancé non plus. Le vrai verrou est en MÉMOIRE, pas en base : sock_connected($id) restait à 1 sur un descripteur fermé, et c'est lui que le balayage interroge pour décider de rejouer l'auto-connect. La base disait une chose, la mémoire l'inverse, et les deux se protégeaient l'une l'autre. Seul un redémarrage du bot vide la mémoire.
✅ Les trois correctifs, livrés en 4.19.15
- Le SID de la configuration est transmis.
::soc::ulines::auto::reconnect_by_idne lisait passerver_sidet appelaitconnect_socketavec huit arguments au lieu de neuf — le neuvième, optionnel, retombait sur sa valeur vide. ⚠ Les trois autres appelants le passaient depuis toujours ; seul ce chemin-ci l'oubliait, et c'est celui qu'emprunte toute rupture de lien. - Les verrous sont rendus. Les quatre sorties d'échec de la phase de burst passent par une sortie commune,
::soc::ulines::auto::echec_burst, qui rendsock_connected,sock_handlesetsock_state, pose l'état et le motif en base, et journalise. ⚠ Rendre le drapeau ne force aucune reconnexion : il rétablit la garde du balayage dans son état vrai, et le cycle d'auto-connect qui existe déjà reprend son travail toutes les 30 s. On ne touche pas à la garde, on cesse de lui mentir. - Un refus du serveur alerte. ⚠
alerte::echecet nonalerte::erreur, contrairement à ce que ce point demandait : un serveur qui redémarre refuse pendant quelques secondes, et c'est normal. Le seuil de trois vaut désormais 90 s, parce que le rejeu existe depuis le correctif n°2 — sans lui, le compteur serait resté à 1 pour toujours, et le raisonnement d'origine était juste.
⚠ Correctif de sûreté associé : next_free_sid comptait le SID de la ligne qu'on est en train de connecter parmi les identifiants « déjà pris », et en cherchait donc un autre. Elle s'exclut maintenant elle-même. La lecture des SID des AUTRES liens reste entière — c'est la garde anti-collision, et deux liens partageant un SID font accepter le SERVER puis rejeter en silence tous les UID.
⚠ Le journal disait trop peu : « SID auto-attribué : 002 » n'indiquait pas quelle valeur venait d'être écrasée. La ligne avait l'air d'une attribution normale sur un lien neuf. Elle distingue maintenant « aucun SID en base » de « SID invalide — REMPLACÉ ».
Le banc : IriX/tests/test-uline-reconnexion.tcl, 28 constats, ramassé par Tools/bancs.sh. ⚠ Il dérive les appelants de connect_socket au lieu de les énumérer — une liste écrite à la main aurait laissé passer exactement celui qui manquait.
⚠⚠ LA PASSE DE MUTATION A PRIS CE BANC EN DÉFAUT DEUX FOIS, et les deux mensonges sont instructifs :
- «
server_sidne figure plus dans le SELECT » passait au vert, parce que le commentaire écrit juste au-dessus de la requête contient le mot. Le banc lisait son propre plaidoyer et le prenait pour une preuve. - « l'exclusion n'est plus appliquée » passait au vert aussi : le constat cherchait la chaîne
id !=dans le texte. Une exclusion calculée puis jamais utilisée laisse la chaîne en place. Chercher un texte prouve qu'on l'a écrit, jamais qu'il agit.
Les deux sont désormais des constats de comportement : le code est extrait de la source et exécuté. Neuf mutations éprouvées, huit détectées.
🧪 PREMIER CONSTAT EN PRODUCTION — 17/08/2026 à 21:19, rupture PROVOQUÉE
Avec l'accord de SerVuS, un seul essai : arrêt du serveur IRC, avis de travaux ouvert avant le geste et refermé par un trap EXIT posé avant.
| Heure | Fait |
|---|---|
| 21:19:31 | systemctl restart overtchatd — le serveur envoie SQUIT 10A, puis ferme |
| 21:19:31 | IriX : « EOF — connexion fermée par le serveur », disconnect_socket |
| 21:19:43 | « auto-connect déclenché par scan » — 12 s plus tard |
| 21:19:43 | « Lien établi avec IriX.overtchat.fr (SID 003) » |
| après | server_sid=003, socket_state=active, connected=oui, retry_count=0 |
✅ Ce que l'essai PROUVE : le lien remonte seul après une rupture réelle, sans redémarrage du bot (le processus a survécu, mesuré par son etime), et server_sid vaut toujours 003 — l'écrasement a cessé.
⚠⚠ CE QU'IL NE PROUVE PAS, ET IL FAUT LE DIRE : il n'a PAS emprunté le chemin corrigé. Le serveur a fermé par EOF, pas par ERROR. Or reconnect_by_id — la procédure qui oubliait le SID — n'est armée que par la branche ERROR de on_read. C'est scan_pass §2 qui a rattrapé, et celui-là a toujours lu server_sid. Ce scénario-ci fonctionnait donc probablement déjà avant le correctif.
⚠ C'est le piège « le banc n'emprunte que la branche saine », refait en réel, sur la production. Trois bancs de lien du dépôt tombent déjà dedans en passant le SID explicitement. Un essai réel n'en est pas exempt : il faut choisir sa rupture, pas seulement en provoquer une.
🧪 SECOND CONSTAT — 18/08/2026, rupture NATURELLE par Ping timeout
Aucun geste, aucun essai provoqué. Le lien est tombé vingt fois dans la journée, et quinze de ces ruptures portent un timer drift à la même seconde — le gel de WSL du point 15. Ces ruptures-là ferment par ERROR, pas par EOF : c'est la branche qui arme reconnect_by_id, celle que le premier essai n'avait pas empruntée.
| Heure | Ce que le journal montre |
|---|---|
| 10:01:37 | ERROR IRC: Closing Link ... (Ping timeout: 80 seconds) → reconnexion dans 30s... |
| 10:02:07 | reconnexion... (SID configuré=003) — 30 s pile, c'est reconnect_by_id |
| 10:18:42 | même enchaînement (Ping timeout: 96 seconds) |
| 10:19:12 | reconnexion... (SID configuré=003) |
✅ Ce que ça prouve : sur la branche ERROR, reconnect_by_id lit server_sid en base et le transmet. Treize reconnexions par ce chemin dans la journée, toutes avec SID configuré=003, et server_sid vaut toujours 003 en base après les vingt ruptures. Le constat qui manquait est pris.
⚠⚠ LE CONSTAT NÉGATIF QU'IL NE FAUT PAS PRENDRE — la cible n'existe plus. Chercher SID auto-attribué au journal ne prouve rien : ce message a été retiré du code par le correctif lui-même, remplacé par « aucun SID en base — attribué » et « SID « X » INVALIDE pour le dialecte — REMPLACÉ par Y ». Son absence était donc acquise d'avance. Ce sont ces deux motifs-là qu'il faut chercher — zéro occurrence le 18/08. La preuve qui porte reste la positive : SID configuré=003 écrit treize fois.
⚠ CE QUE L'ÉPISODE A RÉVÉLÉ EN PLUS : les deux chemins courent une course. reconnect_by_id est armé à 30 s par la branche ERROR, et scan_pass repasse dans le même intervalle. Cinq fois sur vingt, scan_pass a gagné (« auto-connect déclenché par scan ») et reconnect_by_id s'est effacé derrière sa garde — « [id=1] déjà reconnecté, ignoré ». Rien n'est cassé : les deux passent 003, et le doublon est correctement rejeté. Mais le chemin emprunté n'est pas déterministe — il faut lire le journal pour savoir lequel a agi, et c'est précisément ce qui avait rendu le premier essai illisible.
⏪ CE QUI SUIT EST L'ÉTAT D'AVANT LA FERMETURE — PÉRIMÉ, gardé pour la trace. Le critère décrit ci-dessous a été tenu le 24/08/2026 — voir le constat en tête de point. Le second item — voir
echec_burstagir et l'alerte partir — a été pris entre 17:52:45 et 17:55:24 sur la production, avec ses quatre traces. ⚠ Ne pas le relire comme du travail à faire. Un encadré « Pour fermer » périmé sous un titre ✅ est exactement ce qui fait rouvrir un point déjà clos.
Pour fermer — il reste un constat :
- ✅ Une rupture qui produit un
ERROR, pas un EOF — PRIS le 18/08/2026, voir le second constat ci-dessus. Vingt ruptures naturelles, treize passages parreconnect_by_id,SID configuré=003à chaque fois. - Voir
echec_burstagir et l'alerte partir. Un SID volontairement faux en base ferait refuser le lien :socket_state='error',last_errorrenseigné, rejeu toutes les 30 s, alerte au troisième. ⚠ Coupe les services ~90 s — demander l'accord, et poser la remise à003dans untrap EXITAVANT.
⚠ Ce banc ne voit pas une clause SQL qui vide le résultat — mesuré : un WHERE 1=0 ajouté à la requête de next_free_sid le laisse entièrement vert. Son talon de base n'est pas MySQL et ne le sera pas. C'est écrit dans le banc lui-même, pour que personne ne lise ce vert comme une preuve qu'il ne donne pas.
36. ✅ Six boucles pouvaient encore s'arrêter en silence — fermé le 17/08/2026
Les six sont traitées, et un banc les garde.
IriX/tests/test-boucles.tcldérive les mécanismes auto-réarmés et exige qu'aucun appel ne précède le réarmement hors d'uncatch. ⚠ Il a rougi sur quatre d'entre elles avant tout correctif — le gardien d'abord, comme le reste de la journée.⚠ Sa première version n'en voyait que DEUX sur treize : la plupart des boucles ne posent pas leur propre minuteur, elles appellent un planificateur. La table « planificateur → cible » est désormais dérivée du code, pas énumérée — elle ne se périmera pas au premier ajout.
⚠
auto::pub_senda demandé un traitement différent, et le correctif habituel y aurait créé un défaut VISIBLE : son réarmement est conditionnel à dessein — un salon quitté, ou dont la publication est éteinte, ne doit pas se replanifier. Seul ce qui suit les gardes est protégé. Exception assumée : si la lecture du réglage échoue, on ne sait pas s'il faut s'arrêter, et « je ne sais pas » ne vaut pas arrêt définitif — on réessaie.⚠ Deux bancs ont rougi (
test-bindtime.tcl,test-auth.tcl) : scinder une procédure déplace ce qu'ils cherchent. Les deux exigent maintenant la chaîne entière — l'enveloppe appelle le travail, et le réarmement est dans l'enveloppe.⚠ Ce que le banc n'attrape pas, écrit dans son en-tête :
db_topic_checkpasse par untimer_idstocké et lui échappe ; elle a été corrigée à la main. Il ne suit pas non plus les appels en profondeur.
36bis. ✅ (historique) Le recensement qui a mené au point 36 — CONSTATÉ FERMÉ le 30/08/2026, et il annonçait FAUX dans les deux sens
⚠⚠ Ce que la mesure du 30/08/2026 a trouvé — le point était périmé
Les six boucles annoncées « restantes » sont TOUTES traitées, et depuis le 17/08 : chacune porte en commentaire la mention datée de son correctif. L'inventaire dérivé du code donne treize mécanismes auto-réarmés, treize protégés, zéro non protégé. C'est le point 36, marqué fermé, qui décrivait le code réel ; c'est ce point-ci, laissé « historique », qui a continué d'annoncer six chantiers ouverts pendant treize jours. 📌 Un point « historique » n'est pas un point inoffensif : il se lit comme les autres, et personne ne pense à le remesurer.
⚠ Deux boucles ne sont nommées dans AUCUNE de ses deux listes —
ia::scan_inboxetulines::auto::scan, noyées dans le « treize ». Elles étaient protégées avant le 17/08 ; le lecteur du registre ne pouvait pas savoir qu'elles existaient.⚠⚠ Mais il SOUS-disait aussi, et c'est là qu'étaient les vrais défauts
Son affirmation «
chatalerte::scheduleest le seul planificateur qui ne tue pas son minuteur précédent » est fausse. Ils étaient trois, et ce sont exactement les trois qui posent parafter— la file de Tcl, sur laquelle nikilltimernikillutimern'ont la moindre prise :
Planificateur Ce qui manquait Amorçages chatalerte::scheduleaucun tueur, aucune garde 1 raw_log_purge_scheduleaucun tueur, aucune garde 2 — db_initetstart_systemtopic_scheduleune garde, défaite par le rechargement 1 ⚠ Le mécanisme d'accumulation est complet et mesuré :
system_readyettopic_startedvivent dansIriX-src.conf, que tout.rehashre-source et remet à vide. La garde d'amorçage rouvre donc, une seconde chaîne démarre, et l'ancienne n'est pas annulée — personne n'a son identifiant. Pourchatalerte, deux chaînes qui se croisent annoncent deux fois la même alerte,annoncee='oui'n'étant écrit qu'après l'envoi. Un.restart, lui, repart d'un interpréteur neuf et n'accumule rien.⚠ Et deux
killutimerétaient inopérants sans le dire —irix-oper.tcletmodes_du_botpassaient un nom de procédure là où un identifiant de minuteur est attendu. L'appel levait, lecatchavalait, le minuteur survivait. Uncatchautour d'un appel faux le rend indistinguable d'un appel juste.Ce qui a été fait, et pourquoi ainsi
::soc::after_remplacer— une seule procédure, qui balaie la file réelle avant de poser. ⚠ Elle ne garde pas d'identifiant en variable, et c'est la leçon déjà payée parrehash_schedule: une variable vit dansIriX-src.conf, que le rechargement réinitialise — l'identifiant est perdu et le minuteur devient orphelin. La file, elle, ne ment pas. ⚠ Les deuxkillutimerinopérants prennent le balayage en ligne : ils s'exécutent au CHARGEMENT du fichier, avant que cette procédure n'existe.⚠⚠ Le banc, et le trou qu'il avait
Aucun banc ne gardait la règle « un planificateur retire avant de poser », et
test-minuteurs.tclignorait entièrement la fileafter: sa liste de planificateurs exige un crochet devanttimer. Les trois fautifs lui étaient donc invisibles — et àtest-boucles.tclaussi, qui garde le défaut symétrique (une boucle ne doit pas MOURIR ; celles-ci se MULTIPLIAIENT).⚠ La règle neuve est étroite, et ça s'est mesuré avant de l'écrire. « Toute procédure qui pose un
afterdoit retirer » dénonçait onze procédures justes —on_join,on_part,evt_quit, les sockets des ULines — qui posent un délai à un coup, lequel n'a rien à retirer. On ne garde donc que l'auto-réarmement. 📌 Un banc qui accuse onze innocents pour deux coupables finit ignoré, et c'est le coupable qui gagne.⚠⚠ Et la règle a été VERTE À VIDE à sa première exécution : en corrigeant les trois planificateurs, on leur avait retiré le
afterlittéral qu'elle cherchait — elle ne voyait plus aucune boucle. C'est le plancher qui l'a dit, pas le constat. 📌 Une règle écrite sur la FORME fautive meurt avec elle ; il faut qu'elle reconnaisse aussi la forme CORRIGÉE.
Pour fermer ✅ Constaté fermé le 30/08/2026. Les treize boucles sont protégées (mesuré, pas lu) ; les trois planificateurs sans tueur sont corrigés ; les deux killutimerinopérants aussi ; la règle « retire avant de poser » est gardée partest-minuteurs.tcl, cinq mutations rejouées et toutes attrapées. ⚠ Rien n'est en service : IriX charge ces fichiers au démarrage — il faut un.rehashou un redémarrage, donc l'accord de SerVuS. 📌 Un.rehashsuffit et se répare seul : le code neuf balaiera les chaînes anciennes au premier cycle.
Le recensement d'origine, tel qu'il était écrit
Mesuré le 17/08/2026, en dérivant tous les mécanismes auto-réarmés de IriX/cmds/. Treize boucles se replanifient elles-mêmes ; le danger est qu'un réarmement manqué les arrête définitivement — rien ne casse, tout se fige, et le panel continue d'afficher le dernier état comme s'il était frais.
Traité ce jour : botnet::cycle (les deux chemins + l'armement), alerte::cycle_courriels et son armement, ia::bridge::scan (la modération automatique), admin::scan (la boucle-mère, qui porte une dizaine de sous-systèmes).
Restent six, toutes de la même forme — réarmement en dernière instruction, hors de tout catch :
| Boucle | Pas | Ce que son arrêt coûte |
|---|---|---|
db_topic_check | 30 s | ⚠⚠ la plus grave des six : le verrou topic_running reste à 1 et topic_started bloque la relance — elle devient irréparable à chaud, seul un redémarrage du bot la ranime |
auto::scan_topics_tick | 300 s | plus de rattrapage des sujets enregistrés (atténué tant que db_topic_check vit) |
auth::scan | 60 s | les identifications fantômes ne sont plus révoquées — le seul nettoyage des comptes puissants |
raw_log_purge_schedule | 24 h | _raw_log n'est plus purgée ; se voit des semaines plus tard, par le disque |
auto::pub_send | par salon | l'autopublication s'arrête pour ce salon (les autres continuent) |
ia::promote::evaluate | 3600 s | un modèle qui dérive n'est plus rétrogradé, un modèle mûr jamais promu |
⚠ Un défaut distinct, à ne pas confondre : chatalerte::schedule est le seul planificateur du dépôt qui ne tue pas son minuteur précédent. Un second amorçage crée donc une boucle parallèle, et les annonces partent en double — exactement ce que rehash_schedule documente et évite ailleurs.
Pour fermer : appliquer le motif désormais employé quatre fois — le travail dans une procédure à part, appelée sous catch, et le réarmement après, donc toujours atteint. ⚠ Et faire suivre les bancs : découper une procédure déplace ce qu'ils cherchent — test-bindtime.tcl a rougi pour cette raison, et il exige maintenant la chaîne entière (l'enveloppe appelle le travail, et le réarmement est dans l'enveloppe, pas dans le travail).
⚠ Les bind time ne courent PAS ce risque et n'ont pas à être traités : Eggdrop les rappelle depuis sa propre table. Leur mode de panne est l'inverse — un passage manqué n'est jamais rejoué — et il est déjà couvert par la discipline des lignes # RATTRAPAGE:.
35. 🟡 La file d'installation des robots n'avait aucun producteur — BRANCHÉE le 23/08/2026, UN ROBOT EST NÉ le 25/08, et il est né INFIRME
Le 25/08/2026 — le robot est né, la chaîne marche, et elle a montré deux trous
✅ SerVuS a cliqué, et la chaîne entière a fonctionné. Demande #2 déposée à 18:45:25, traitée à 18:46:38 : répertoire choisi, port telnet déduit du disque (3345, les quatre autres occupant 3341-3344), Eggdrop lancé sous une unité transitoire, statut
installingécrit. Ce que ce point attendait — « voir un robot en naître » — est constaté.⚠⚠ MAIS IL EST NÉ SANS SALON GARDIEN ET SANS NOM DE BOTNET.
set botnet-nicketchannel add #!Bots!avaient été posés à la main suroverop-h/-q/-s/-tle 15/08, et reportés dans aucun des deux écrivains duoverop.conf.OverOp-Aest le premier robot né du modèle seul : il est arrivé vivant, connecté, et invisible du parc —::soc::botreg::salons_attenduscompte par le salon gardien. ➜ Corrigé dans les deux écrivains, avec six constats de non-divergence danstest-botpool.php. Le robot a été retiré.⚠⚠ ET LA FENÊTRE DU
helloEST RESTÉE OUVERTE 45 MINUTES SUR UN RÉSEAU PUBLIC. Le robot attendait/msg OverOp-A hello, qui n'est jamais venu. Pendant tout ce temps, le premier venu qui tapait ce mot devenait propriétaire — et la ligne insérée dans le userfile ne se défait pas en restaurant une sauvegarde.⚠⚠ CE N'EST PAS UNE HYPOTHÈSE : C'EST DÉJÀ ARRIVÉ TROIS FOIS. Mesuré le 25/08 —
OverOp-Happartenait àClaudeEssai,OverOp-QetOverOp-SàClaudeTest. Le handleSerVuSn'existait dans aucun des trois userfiles. ⚠set owner "SerVuS"y figurait pourtant : cette ligne est inerte — Eggdrop y désigne des propriétaires parmi les handles existants, il n'en crée jamais.✅ Parc nettoyé le 25/08, sur décision de SerVuS :
OverOp-H,-Q,-Set-Aretirés — unités, dossiers,IriX_botreg_pooshy,IriX_botnet, la file. Restent IriX et OverOp-T, le seul robot qui appartienne vraiment à SerVuS. ⚠IriX_botregétait vide : aucun prêt n'a été cassé. Sauvegarde complète prise avant, dans/home/servus/sauvegarde-robots-*.⚠ Reste, et il faudra arrêter IriX à froid : l'entrée
OverOp-Hen+bdansIriX/runtime/IriX.user, avec son--PASS2en clair. À grouper avec le chantier des secrets botnet aléatoires.
⚠⚠ RENVERSEMENT DE DOCTRINE, DÉCIDÉ PAR SERVUS LE 25/08/2026 — le hello s'automatisera. Le projet écrivait le contraire à quatre endroits : Tools/overop-pool.php (« LE HELLO NE S'AUTOMATISE PAS, et c'est voulu : il désigne un PROPRIÉTAIRE »), PRET-ROBOT.md, IRIX.md, Web/inc/botnet.php.
La raison du renversement, et elle est mesurée : la doctrine protégeait contre « un compte se crée tout seul ». Appliquée, elle a produit trois robots appartenant à des comptes d'essai et une fenêtre de course ouverte trois quarts d'heure sur un réseau public. ⚠ Et ce qui est proposé ne crée aucun compte : il recopie un propriétaire qui existe déjà dans IriX.user, avec son empreinte. Ce n'est pas la même chose.
⚠ La fenêtre ne sera pas raccourcie, elle sera SUPPRIMÉE. Eggdrop n'ouvre hello que tant qu'aucun handle ne porte le flag n (learn-users vaut son défaut, 0). Un userfile écrit avant le premier démarrage ferme la porte dès la première seconde. Faire envoyer le hello par IriX l'aurait seulement raccourcie — et l'aurait laissée dépendre d'IriX étant en ligne.
⚠ Le hash est transférable, c'est vérifié : le modèle charge pbkdf2, « celui que charge déjà IriX sur cette machine ». La ligne SerVuS - jlmnoptx d'IriX.user se recopie telle quelle.
⚠⚠ MAIS LE SECRET DU LIEN BOTNET, LUI, NE PEUT PAS ÊTRE HACHÉ — et ce n'est pas une négligence, c'est le protocole : le défi-réponse d'Eggdrop exige le clair des deux côtés. C'est pourquoi IriX.user porte des --PASS2 en clair. ➜ Le correctif n'est pas de le cacher, mais de le rendre aléatoire et unique par lien — aujourd'hui ce sont deux mots de dictionnaire.
✅✅ (1) L'APPROPRIATION AUTOMATIQUE EST FAITE ET CONSTATÉE — 25/08/2026 à 20:45. Le poseur recopie le propriétaire d'IriX.user dans un userfile neuf avant le premier démarrage. Le journal de la pose réelle :
20:44:59 Demande #2 : OverOp-A (déposée par SerVuS)
20:44:59 propriétaire recopié sans « hello » : SerVuS — 1 salon(s)
20:44:59 userfile présent — le robot a un propriétaire.
20:45:05 déclaré au parc (origine « auto »)
20:45:05 ✅ OverOp-A est sous systemd et actif.
20:45:14 OverOp-A joined #!Bots!
20:45:28 parc : OverOp-A : online
Six secondes, et aucune unité transitoire créée — le cercle que systemd-run -m rompait n'existe plus, donc la pose tient en un seul temps. ⚠ Constaté aussi : un seul handle porte le flag n dans le userfile produit, donc hello refuse dès la première seconde. La fenêtre n'est pas raccourcie, elle n'existe plus.
⚠ Le repli est conservé et il compte : sans propriétaire à recopier — un tiers qui vient d'installer le projet —, botpool_proprietaire() rend null et l'ancien chemin avec hello reprend. C'est la part de la doctrine d'origine qui survit : recopier oui, inventer jamais.
⚠⚠ ET UN DÉFAUT DÉCOUVERT EN ÉPROUVANT — il vaut pour le PANEL aussi. IriX_botreg_pool porte une contrainte d'unicité uk_name : un robot retiré ne peut pas être redemandé sous le même nom, sa ligne close occupant la place. Le dépôt a échoué sur Duplicate entry 'OverOp-A' for key 'uk_name' — et le bouton du panel se heurterait exactement au même refus, sans que rien ne l'explique à l'exploitant. ➜ À traiter avec le morceau (3) : ce que « supprimer un robot » veut dire doit inclure sa ligne de file.
| Ce qui reste | (2) le lien botnet posé à la pose, avec un secret aléatoire par lien — ⚠ il sera en clair des deux côtés, le défi-réponse d'Eggdrop l'exige, et c'est déjà le cas aujourd'hui avec deux mots de dictionnaire ; plus une file d'attente de liens. (3) ⚠ sa MOITIÉ BASE est faite le 26/08/2026 (voir l'encadré ci-dessous) ; il reste les trois endroits SYSTÈME, et une décision. |
|---|
⚠ (3) — la moitié BASE est faite le 26/08/2026, et elle DIT ce qu'elle ne fait pas
Le bouton « Retirer » existe dans « Robots de service — déclaré contre observé », au niveau propriétaire — un cran au-dessus de poser, parce que poser est réversible et retirer ne l'est pas.
botpool_retirer()défait les endroits en BASE, dans cet ordre : les mandats passent àfini(ils ne sont jamais effacés — un mandat est la trace de qui a accordé quoi), la présence (opmybot), le pair de botnet (parbotnet_remove(), qui porte déjà son refus « le lien est monté »), le parc (pooshy), et enfin la ligne close de la file — celle deuk_name, qui réservait le nom pour toujours. ⚠⚠ LE CHIFFRE EST RETIRÉ le 27/08/2026, et c'est une correction, pas un allègement. Cette phrase annonçait « quatre des sept endroits » — puis en énumérait cinq, dans la même phrase. Le docbloc de la fonction faisait la même chose, et appelait la ligne de file « le septième endroit » d'un total qui en compte huit (cinq en base, trois système). Un compte écrit à la main ne survit pas à la séance qui l'écrit : on énumère, on ne dénombre plus. Un banc exige désormais que chaque table réellement écrite soit nommée dans le docbloc, et qu'aucun compte n'y revienne. ⚠ Les lignespendingetinstallingsont épargnées : les effacer laisserait l'exécutant poser un robot que plus rien n'attend.⚠⚠ ET LES TROIS AUTRES SONT NOMMÉS DANS LE MESSAGE, un par un, au lieu d'être tus. C'est le cœur de ce qui a été écrit : un retrait qui se dirait complet refabriquerait exactement le résidu qu'on mesure aujourd'hui.
Ce que la mesure du 26/08 a montré —
OverOp-H, retiré à la main le 25/08 : plus d'unité, plus de dossier, mais son entrée+best toujours dansIriX.user(ligne 2) et un mandat#accueilsuspendu(id 14) pointe encore sur lui. EtOverOp-A, né le 25/08, est dans le parc et sur#!Bots!mais n'a aucune ligne dansIriX_botnet— le robot « infirme » du morceau (2), pris sur le fait.Éprouvé :
Web/tests/test-botpool-retrait.php, 34 constats, plus une passe de mutation à la main sur 11 blessures — aucun trou. Les 57 bancs du site restent au vert.⚠ CE QUI RESTE, et pourquoi ce n'est PAS un oubli :
Endroit Pourquoi le site ne le fait pas l'unité overop@<lettre>.servicesystemctl stop/disable overop@…n'est pas danssudo -n— mesuréle dossier /home/servus/overop-<lettre>Tools/overop-pool.phppose lui-même la doctrine : « unrm -rflancé par un service root sur un chemin construit à partir de la base est un risque plus grand que ce qu'il évite »l'entrée +bd'IriX.userEggdrop réécrit son userfile ; toute édition à chaud est perdue sans un mot. Exige d'arrêter IriX ⚠⚠ LA DÉCISION QUI ATTEND SerVuS. Le registre disait « étendre
Config/sudoers.d/overtchat-servicesaux seules unitésoverop@*». Le fichier n'a pas été touché, et c'est délibéré : son en-tête pose deux règles écrites que cette extension enfreindrait — « Pas de joker » et « Ni enable, ni disable, ni mask, ni edit : le panel pilote des services, il ne reconfigure pas la machine ». Étendre demande donc de changer une doctrine, pas d'ajouter une ligne.Si SerVuS le veut, la forme la plus étroite est
overop@?.service— un seul caractère, ce qui correspond exactement à la gardebotpool_lettre()(OverOp-suivi d'UNE lettre) et refuseoverop@ab.servicecommeoverop@../x.service. Sinon, les trois gestes restent ceux de l'exploitant, et le message les donne déjà écrits, prêts à recopier.
✅✅ TRANCHÉ ET BRANCHÉ LE 23/08/2026 : un BOUTON au panel, pas un déclencheur automatique. SerVuS a choisi que le parc ne grandisse jamais sans quelqu'un devant l'écran — ce qui importe, puisque l'exécutant pose de vrais Eggdrop. Grade requis : Admin, le même que décider un prêt.
⚠ Le motif suivi est celui des trois autres files du projet, qui marchent : modq, botreq, chanq. Le demandeur dépose une ligne, l'exécutant vérifie et agit, un statut porte la vérité. botreg_pool était la seule des quatre dont personne ne remplissait la file.
⚠⚠ ET LE MIROIR PHP N'AVAIT QUE DEUX GARDE-FOUS SUR QUATRE — trouvé en branchant, pas en relisant. Il connaissait plafond et echecs, ignorait encours et trop_tot. Personne ne l'avait vu parce qu'il ne servait qu'à AFFICHER un avertissement : une moitié de garde-fou qui n'a jamais rien gardé ne se remarque pas. encours est le plus important pour un bouton — sans lui, deux clics déposent deux lignes et deux robots naissent.
⚠⚠ UN BANC A ATTRAPÉ UNE ERREUR DE MA MAIN : j'avais supposé une colonne nick qui s'appelle name. test-colonnes.php croise chaque colonne citée dans le PHP avec le schéma réel, et il a rougi. Sans lui, la requête aurait levé en production au premier clic, dans un try qui l'aurait avalée en un message poli et faux.
✅ Les trois affirmations d'une chaîne automatique inexistante sont retirées : le tableau de ce cahier, l'en-tête de Tools/overop-pool.php, le commentaire de l'unité systemd, et le texte affiché par la page du panel. ⚠ L'argument de la page — « deux chemins d'écriture donneraient deux façons de poser la même décision » — est conservé, parce qu'il reste juste : la file n'en avait aucun. Le bouton ne crée pas un second chemin, il crée le premier.
⚠ Trois corrections à l'énoncé ci-dessous, mesurées le 23/08 : le §7.6 de PRET-ROBOT.md avait déjà été amendé le 17/08 (le manque était ailleurs) · le comptage est de six occurrences et cinq commits, pas quatre · et places_restantes n'est pas « la mesure qui devrait déclencher » mais un doublon Tcl mort de botreg_places(), bien vivante côté PHP.
État : 31 constats à test-botpool-panel.php, 14 mutations, 14 attrapées — dont la divergence PHP/Tcl dans les deux sens. En 4.19.47.
Trouvé le 17/08/2026 en instruisant la « branche morte » botreg/pool-arrete du point 24. Le défaut est bien plus large que l'alerte qui l'a fait remarquer.
::soc::botreg::demander_robot (IriX/cmds/irix-botreg.tcl) détient les deux seuls INSERT INTO …_botreg_pool de tout le dépôt — et elle n'a aucun appelant en production. Quatre occurrences dans le dépôt entier : sa définition, un commentaire, un banc, et le registre.
⚠⚠ Donc la file du pool n'a aucun producteur. Aucun code, Tcl ou PHP, ne sait y écrire une ligne. Et ce n'est pas un reste d'ancien code : git log -S rend quatre commits, tous en ajout, aucune ligne d'appel supprimée. Elle n'a jamais été appelée, à aucun moment de l'histoire.
La cascade, mesurée :
| Maillon | État |
|---|---|
| le déclencheur (« le seuil est franchi ») | ❌ n'existe pas — aucun bind time, aucun utimer dans ce module |
places_restantes, la mesure qui devrait déclencher | ❌ aucun appelant de production non plus |
demander_robot — dépose la ligne pending | ❌ jamais appelée |
pool_etat / pool_verdict / pool_motif / nom_libre | ❌ atteints uniquement par elle, donc jamais |
l'alerte botreg/pool-arrete | ❌ morte par voie de conséquence |
le consommateur Tools/overop-pool.php + son timer | ✅ en service, et il tourne à vide |
⚠ Conséquence à retenir : la ligne du pool qui a servi à poser OverOp-T les 10-11/08 n'a pu être écrite qu'à la main — aucun code du dépôt ne sait le faire.
⚠⚠ ET C'EST L'ASYMÉTRIE QUI A TROMPÉ. Le registre a identifié puis comblé la moitié consommatrice le 09/08, et déclaré « la POSE est close ». Personne n'a remarqué que la moitié productrice n'était pas branchée : on a vérifié que le repas serait mangé, jamais qu'il serait cuisiné. C'est le motif « à la jointure » à son échelle la plus large — du code juste des deux côtés, et rien au milieu.
⚠ Et la documentation l'affirme au présent. Markdown/PRET-ROBOT.md §7.6 range « constate le seuil franchi, engendre le nom, dépose une ligne pending » dans la colonne d'IriX, coche l'exécutant comme livré, et ne marque aucun manque du côté demandeur.
| ⚠ Pour fermer | La décision est prise et le code est écrit ; il ne manque qu'un vrai clic. Depuis le panel, en Admin : le bouton « Demander un robot de plus » dépose une ligne pending, et le service la ramasse dans les cinq minutes. ⚠⚠ Il posera un VRAI robot — c'est le seul constat de ce point qui ne se simule pas, et le prochain nom serait OverOp-A. ⚠ Mesuré sans cliquer : le verdict des quatre garde-fous rend ok, le bouton s'afficherait, 4 robots sont déclarés sur 12. |
|---|
- brancher — un balayage appelle
places_restantespuisdemander_robot; - rendre le geste explicite — un bouton au panel, qui dépose la ligne ;
- assumer le manuel — et alors le dire partout, en retirant de
PRET-ROBOT.mdce qui décrit une chaîne automatique qui n'existe pas.
⚠ Ne pas brancher sans le banc de bout en bout : le consommateur pose des robots pour de vrai, et un déclencheur trop bavard remplirait le parc.
34. ✅ Une campagne du serveur était ROUGE sans que personne l'entende — fermé le 17/08/2026
Mesuré le 17/08/2026. Servers/overtchat-hybrid/tests/run-tests.sh, campagne 14/17 (test-irix.py, « le vrai code de service se lie au serveur ») : 9 réussites, 13 échecs. Le lien s'établit, le burst se clôt, mais ::soc::ulines::auto::inject_uids lève une erreur Tcl en appelant inject_uid_from_def, et aucun des cinq pseudo-clients n'est injecté (def=0 sql=0). Tout ce qui suit tombe en cascade.
⚠ CE N'EST PAS UNE RÉGRESSION, et c'est ce qui rend le point grave. Vérifié en remettant le s_serv.c du commit précédent et en recompilant : la campagne échoue à l'identique. Le défaut est antérieur, et personne ne sait de quand.
⚠⚠ POURQUOI IL DORMAIT — une neuvième façon pour un banc de mentir. Les huit connues supposent un banc vert à tort. Celui-ci est franchement rouge : il dit la vérité, et personne ne l'entend. Tools/bancs.sh range run-tests.sh parmi les bancs « qui exigent un serveur en face » et le déclare non lancé — alors qu'il n'exige rien d'autre que le binaire, présent. Le lanceur ordinaire annonce donc « 81 bancs au vert » en toute honnêteté, au-dessus d'une campagne rouge qu'il a lui-même écartée.
⚠ Ne pas conclure de la production : les cinq services sont bien présents en service (mesurés dans ${t}_online, is_svc='oui'). Le défaut est donc dans ce que le banc monte, pas nécessairement dans IriX — mais un banc dont on ne sait pas dire s'il accuse le code ou lui-même ne protège rien.
✅ Constaté fermé le 17/08/2026 — et le coupable n'était pas IriX
La cause, lue dans la trace complète : commande inconnue : validchan. C'est une commande d'Eggdrop, et le banc exécute le code d'IriX sous tclsh, qui ne la connaît pas. desarmer_flood_join l'appelle, et cet appel a été ajouté au chemin de repli bien après l'écriture du banc. Le code d'IriX est juste — en service, les cinq services sont bien présents. C'est le banc qui était incomplet.
⚠ La trace n'était lisible nulle part, et c'était le vrai obstacle. Le banc python affichait banc.read()[-1500:] : une tranche de FIN, alors que le nom de la commande manquante est au DÉBUT. Le banc possédait déjà _extrait(), écrit pour ce problème exact — il n'était pas appliqué ici. Le message complet vivait dans irix.out, au fond d'un mkdtemp que personne n'ouvre.
Les trois correctifs, et le troisième est le plus important :
irix-link.tcl— talonsvalidchanetchannel, avec leur limite écrite : ils ne prouvent rien d'Eggdrop, cette campagne éprouve le protocole.irix-link.tcl+test-irix.py— toute commande sans talon s'annonce désormais sur une ligneMANQUE|, et un constat dédié la nomme au lieu de treize échecs muets. Placé avant le verdict, qu'il explique.- ⚠⚠
Tools/bancs.sh— le classement était faux.run-tests.shétait rangé parmi les bancs « qui exigent un serveur en face » : c'est inexact, il démarre les siens. Il n'exige que le binaire compilé. Il a désormais sa propre section et tourne par défaut là où le binaire existe ; là où il manque, il est nommé avec la raison plutôt que compté en échec.
Constaté : Tools/bancs.sh rend 82 bancs au vert (81 + la campagne), et run-tests.sh 17 campagnes, 0 échec. Deux mutations éprouvées : le binaire retiré → « non lancé — binaire absent », sans échec compté ; le talon retiré → le constat « aucune commande d'Eggdrop ne manque à ce banc » rougit en tête.
⚠ Piège rencontré en chemin, et il fait perdre du temps : une campagne interrompue laisse un serveur d'essai orphelin qui garde son port, et fait échouer les campagnes suivantes — l'échec accuse alors le code. Les reconnaître à leur configuration dans /tmp/overtchat-* ; ⚠ jamais un pkill large, la production tourne le même binaire.
⚠ Et une seconde fois le même piège que le 16/08 : lancées depuis /mnt/c, ces campagnes utilisent le build/ du clone Windows — daté du 7 août. Deux campagnes accusaient mes bancs pour un binaire périmé. Une mesure prise au mauvais endroit est fausse.
33. ✅ IriX_sessions.ip mentait sur les visiteurs du webchat — corrigé ET constaté en service le 17/08/2026
Mesuré le 16/08/2026 en base de production. Pour un visiteur du webchat, le serveur IRC voit son adresse réelle et l'écrit dans hostname ; la colonne ip, elle, reçoit 127.0.0.1.
| id | nick | ident | hostname | ip |
|---|---|---|---|---|
| 774 | SerVuS | webchat | 192.168.1.254 | 127.0.0.1 |
| 773 | Paul | webchat | 192.168.1.254 | 127.0.0.1 |
| 772 | SerVuS | webchat | 192.168.1.254 | 127.0.0.1 |
26 lignes portent ce désaccord. Les valeurs distinctes de la colonne sont seulement trois : vide (515, les pseudo-clients de services), 127.0.0.1 (237) et 192.168.1.254 (15) — aucune adresse publique n'y a jamais figuré.
⚠⚠ CE DÉFAUT EN A FABRIQUÉ UN AUTRE, SUR LE PAPIER. Qui mesure le plafond de connexions depuis la base conclut que tous les visiteurs sont sur la boucle locale, donc qu'ils partagent un compteur. C'est exactement ce qu'affirmait le point 28, et c'était faux : le journal du serveur écrit au même instant « Connexion de Paul [webchat@192.168.1.254] … [clients] ». Une colonne fausse ne casse rien de visible — elle fait écrire des diagnostics faux, qui coûtent ensuite des séances entières.
Le module concerné est IriX/cmds/irix-online.tcl (decode_ip, on_uid), qui porte déjà une note sur un défaut voisin de la même colonne dans ${t}_online — la colonne y avait contenu :Sonde.
Pour fermer : trouver qui ÉCRIT la colonne, et non qui la lit — c'est la méthode des cinq portes de l'identification. Puis un banc qui compare, sur une même session, ce que le serveur journalise et ce que la base enregistre. ⚠ Ne pas « corriger » en recopiant hostname dans ip : les deux colonnes ne disent pas la même chose, hostname peut porter un nom résolu ou un masque, et c'est justement ce genre de raccourci qui a produit le défaut.
Ce qui a été trouvé le 17/08/2026 — et ce n'était pas dans IriX
La colonne n'était pas écrite par IriX : elle recopie fidèlement le champ IP de la commande UID. C'est le serveur qui annonçait 127.0.0.1.
adopter_ip_relayee() (Servers/overtchat-hybrid/src/websocket.c) pose l'adresse relayée dans les trois champs textuels — sockhost, realhost, host — et jamais dans conn->ip, la sockaddr figée à l'accept(), qui reste celle du relais. Or encode_ip() (Servers/overtchat-hybrid/src/s_serv.c), seule à produire ce champ, lisait la sockaddr en priorité pour tout client local. D'où le désaccord exact du tableau ci-dessus : l'hôte vrai, l'adresse fausse.
Mesures qui ont tranché, et qui valent mieux qu'une relecture :
| Champ | Écrivains | Lecteurs |
|---|---|---|
conn->ip | 2 (s_bsd.c, à l'accept et au connect) | 1 seul : encode_ip |
conn->aftype | 2 | aucun |
sockhost | accept, connect, et l'adresse relayée validée | bans, blocs allow, plafond par IP |
sockhost n'est jamais un nom résolu : c'est la forme textuelle de l'adresse, et celle que le serveur tient pour vraie partout ailleurs. Le champ IP de UID était le seul endroit à en dire une autre. Correctif : encode_ip lit sockhost d'abord, conn->ip en repli. Un seul lecteur touché, aucun autre comportement déplacé.
Banc : Servers/overtchat-hybrid/tests/test-forward.py monte désormais un lien de services et lit la commande UID au fil de l'eau (+5 constats, 12 au total). ⚠ Ses sept constats d'origine restaient VERTS sur le code fautif — ils ne lisaient que ce que le serveur dit AU CLIENT (l'hôte du 302), jamais ce qu'il dit aux services. C'était l'angle mort exact. Passe de mutation faite : la garde annulée, deux constats rougissent en rendant 127.0.0.1.
✅ Constaté fermé en production le 17/08/2026 à 10:04:22
Le binaire a été recompilé et mis en service (redémarrage autorisé par SerVuS), puis une connexion de contrôle a emprunté le chemin exact du webchat — 127.0.0.1:8000, TLS, en-tête X-Forwarded-For — sans parler dans aucun salon. Ce qu'IriX a écrit :
| id | nick | ident | hostname | ip |
|---|---|---|---|---|
| 780 | ClaudeVerif | webchat | 198.51.100.77 | 198.51.100.77 |
Les deux colonnes concordent, et c'est la première adresse non locale que cette colonne ait jamais portée : ses valeurs distinctes passent de trois à quatre, la quatrième ne comptant que cette ligne.
⚠ Effet de bord constaté, et il n'était pas prévu : ${t}_online portait ip et host vides sur ses dix lignes avant le redémarrage — le repli de session_open, qui va y chercher une adresse manquante, ne pouvait donc rien compléter. Le reflet reconstruit les renseigne. Deux mécanismes se réparent d'un seul correctif, ce qui veut aussi dire que le second n'était éprouvé par personne.
⚠ Les 26 lignes déjà en base restent fausses : c'est un historique, on ne le réécrit pas. Elles se lisent avec la date du correctif en tête.
31. ✅ Dix-huit encarts du panel se rendaient NUS — fermé le 17/08/2026
Mesuré le 16/08/2026, en cherchant où afficher l'état de la liste des villes.
Web/inc/layout.php ne lie qu'une seule feuille au panel — app.css, une ligne <link>, aucun @import. Or .note et .stat-card n'y existent pas : elles ne sont déclarées que dans site.css, la feuille du site public.
| Classe | Déclarée dans app.css | Déclarée dans site.css | Écrite dans le panel |
|---|---|---|---|
.note | 0 | 7 | 18 fois, dans 8 fichiers |
.stat-card | 0 | 3 | 3 fois |
Les fichiers : Web/pages/alertes.php, botnet.php, chat.php, dashboard.php, eggdrop.php, opers.php, robots.php, et ⚠⚠ Web/inc/alerte.php — le bandeau d'alerte critique affiché sur TOUTES les pages du panel. Un élément dont la classe n'existe pas se rend sans cadre, sans couleur, sans rien qui le distingue du texte courant : l'avertissement le plus grave du panel s'affiche donc comme un paragraphe ordinaire.
⚠⚠ ET SON GARDIEN EST AVEUGLE. Web/tests/test-vues.php existe pour dire « une classe écrite en dur doit exister dans une feuille » — mais il fait foreach (['app.css', 'site.css'] as $f) et concatène les deux avant de chercher. Toute classe déclarée dans l'une passe pour disponible dans l'autre. C'est le défaut que Markdown/WEB.md raconte déjà dans l'autre sens, avec le même angle mort. Même cause pour class="alert error" dans Web/pages/robots.php : .alert.error n'existe pas — seuls ok, err, warn, info —, mais .dot.error fait passer le détecteur au vert.
Pour fermer : rendre le contrôle conscient de la feuille que chaque page charge réellement (le panel app.css, le site site.css), puis reprendre les 21 écritures. ⚠ Le banc doit rougir AVANT la reprise, sinon il ne prouve rien. ⚠ Et ne pas « corriger » en ajoutant site.css au panel : les deux feuilles ont des vocabulaires distincts, et les charger toutes deux ferait entrer dans le panel des règles pensées pour le site public.
✅ Fermé le 17/08/2026 — et le banc a trouvé DEUX défauts de plus
Le banc a été repris d'abord, et il a rougi avant toute correction : 11 classes nues dans 8 fichiers, puis 2 classes composées. Quatre aveuglements distincts ont été levés, pas un :
| Ce qui aveuglait | Ce que ça cachait |
|---|---|
| les deux feuilles concaténées avant recherche | les 18 .note et 3 .stat-card, couverts par site.css que le panel ne charge jamais |
le périmètre borné à pages/ | Web/inc/alerte.php — le bandeau d'alerte critique, rendu sur toutes les pages |
| tout attribut portant du PHP jeté en entier | 9 écritures de plus, dont dashboard.php et robots.php |
| les jetons testés un par un, jamais composés | .alert.error et .form-grid.c3 |
⚠⚠ Les deux composées étaient inconnues du registre, et l'une n'était pas cosmétique : botnet.php demandait class="form-grid c3" alors que seul .form-grid.c2 est déclaré — la grille se rendait sur une seule colonne. La règle manquante a été ajoutée. robots.php écrivait class="alert error" là où .alert connaît ok, err, warn, info : le frein de capacité s'affichait sans sa couleur, comme une information banale.
⚠ La règle du contrôle composé est DÉRIVÉE : une base est « à modificateurs » si la feuille déclare au moins une règle .base.quelquechose, et un jeton employé avec elle doit exister soit en composition, soit déclaré seul. Cette seconde condition est indispensable — sans elle, .error passait pour disponible en s'appuyant sur .dot.error, une pastille de sept pixels sans rapport.
⚠ Un faux positif rencontré et corrigé : retirer la partie PHP d'un attribut laisse des fragments (class="pill st-…" → st-). Un jeton finissant par un tiret n'est pas une classe ; deux pages justes étaient accusées.
Mutation : un encart repassé à .note fait rougir le banc en nommant le fichier. Reprise : 8 fichiers, 21 écritures, plus 3 ternaires qui rendaient une variante vide — un encart sans couleur ne se distingue pas du texte.
32. ✅ ?p=update était un lien mort au tableau de bord — fermé le 17/08/2026
Mesuré le 16/08/2026. Web/pages/dashboard.php propose la version publiée dans un lien href="?p=update". La route update n'est déclarée ni dans panel_routes() ni dans panel_routes_hors_table() de Web/inc/routes.php. Le repli ramène donc au tableau de bord, sans un mot — on clique, la page se recharge, et rien n'indique que la cible n'existe pas.
C'est le défaut exact que l'en-tête de Web/inc/routes.php raconte pour la page « Protection », reparu sur un href que test-routes.php ne lit pas : ce banc croise nav_items(), panel_routes() et les fichiers de pages/, mais aucun banc ne dérive les href="?p=…" écrits dans les pages.
Pour fermer : d'abord trancher ce que ce lien devait ouvrir — la page de mise à jour n'existe pas, il n'est donc pas sûr qu'elle ait jamais été écrite. Puis ajouter au banc la dérivation des href="?p=…", qui aurait attrapé celui-ci et attraperait les suivants.
✅ Fermé le 17/08/2026 — il n'y avait pas de cible à inventer
⚠ Le nom n'était pas libre. ?p=update est servi — par Web/public/index.php, comme point d'appel JSON du site, celui qu'un client interroge pour savoir s'il a du retard. Un banc qui aurait demandé « ce nom existe-t-il quelque part ? » serait resté vert : la seule question utile est « est-ce une route du PANEL ? ».
Et le panel avait déjà tranché. La pastille « vX disponible » de Web/inc/layout.php, en tête de toutes les pages, pointe vers ?p=statut — une route qui existe. Deux endroits du même panel annonçaient la même chose et menaient ailleurs : il n'y avait qu'une divergence à supprimer.
Le banc : Web/tests/test-routes.php dérive désormais les href="?p=…" de toutes les pages du panel et exige que chaque route soit déclarée. ⚠ Son périmètre s'arrête au panel, et il a fallu le mesurer : la première version balayait tout Web/inc/ et accusait dix liens justes — Web/inc/site.php écrit la navigation du site public, dont les routes ne sont pas celles du panel. C'est la même frontière que celle des deux feuilles de style au point 31, rencontrée le même jour dans un autre outil.
Mutation : le lien mort remis, le banc rougit en rendant dashboard.php → ?p=update.
30. ✅ Un prêt SUSPENDU n'expirait jamais — CONSTATÉ FERMÉ le 23/08/2026, sur une trace vieille de six jours
✅✅ CONSTATÉ FERMÉ LE 23/08/2026 — ET LA PREUVE DORMAIT EN BASE DEPUIS LE 17/08. Il n'y avait rien à fabriquer : le prêt d'essai de #membres (OverOp-S) est le cas du critère, et son histoire est écrite au journal des prêts.
| Quand | Ce qui s'est passé |
|---|---|
| 13/08 18:21:03 | prêt accordé, échéance au 16/08 18:21:03 |
| 14/08 19:00:00 | absence constatée — absent, suspension dans 24 h |
| 15/08 19:00:00 | suspension automatique — suspendu, absent depuis 1440 min |
| 16/08 18:21:03 | échéance dépassée — et rien ne se produit |
| 17/08 12:12:53 | le correctif est commité (d1aad7a) |
| 17/08 17:34:20 | le robot redémarre — il charge le nouveau code |
| 17/08 18:00:00 | le balayage horaire le termine — échéance atteinte |
⚠ L'ordre des faits est ce qui prouve : le mandat a traversé l'échéance sans bouger tant que l'ancien code tournait, et il s'est terminé au premier balayage horaire suivant le chargement du correctif. État final : etat = fini, motif_fin = échéance atteinte, updated_at = 17/08 18:00:00.
La ligne au journal du robot, qui a survécu à la rotation :
août 17 18:00:00 SOC MANDAT: prêt de #membres terminé (OverOp-S) — échéance atteinte
⚠⚠ PERSONNE N'AVAIT REGARDÉ. Le point est resté 🟡 six jours alors que son critère était rempli — c'est le symétrique du registre qui sur-dit : il sous-disait. Avant d'aller fabriquer une condition, il faut regarder si elle ne s'est pas déjà produite.
⚠⚠ ET LE CONSTAT A TROUVÉ UN AUTRE DÉFAUT — c'est sa valeur ordinaire dans ce projet. Le journal porte DEUX lignes fin pour cette seule fin (ids 15 et 16, même seconde) : terminer journalise, et balayer journalisait aussi pour marquer expire plutôt que ok. Chacune avait raison séparément. Les expirations d'avant le 14/08 — ids 7 et 9 — n'en ont qu'une. Corrigé en 4.19.48 : balayer passe l'issue au lieu de réécrire.
⚠⚠ ET LE BANC BOUCHONNAIT terminer — deux mutations du vrai code le laissaient vert. C'est le défaut de ce point, une seconde fois : il bouchonnait echus, et sa clause WHERE n'était éprouvée par personne. La parade trouvée alors — charger la VRAIE procédure — est désormais appliquée aux deux. 213 vérifications, 6 mutations, 6 attrapées.
La question qui bloquait avait DEUX réponses
« Quand un prêt passe à suspendu, le robot est-il déjà sorti ? » — c'est elle qui décidait si un
IN ('actif','suspendu')suffisait. Mesuré :· suspension AUTOMATIQUE (
suspendre_non_servis) — le robot est absent par définition : c'est son absence prolongée qui déclenche la suspension. Le retrait y sera un ordre sans effet, seulement journalisé. · suspension MANUELLE (MANDATOFF) — le robot RESTE ASSIS DANS LE SALON. Contrairement àMANDATFINjuste à côté, ce chemin n'appelle jamaisretirer. C'est là que le balayage manquait vraiment.Le
INsuffit donc, et il répare le second cas. ⚠fin IS NOT NULLest conservé : un prêt suspendu sans échéance n'est toujours pas concerné. ⚠ Ne pas propager ceINànon_servisni_reprendre_un, qui filtrentactifà dessein — leur banc le verrouille.⚠⚠ ET LE BANC REMPLAÇAIT CE QU'IL PRÉTENDAIT GARDER.
test-mandat.tclbouchonnaitechusd'un bout à l'autre (proc … echus {} { return $::ECHUS }). Tout ce qui suit son appel était éprouvé — l'ordre des gestes, les échecs, le journal — et sa clauseWHERE, par personne. C'est exactement là que vivait le défaut. Le banc charge désormais la vraie procédure et lit la requête qu'elle envoie : les deux états exigés séparément, et la gardefin IS NOT NULLexigée aussi — l'élargir sans elle terminerait tous les prêts perpétuels au premier passage, un défaut bien pire que celui réparé.⚠ Reste à constater sur un prêt réel arrivé à échéance en étant suspendu.
Trouvé en attendant une péremption réelle, pas en relisant le code. Le prêt d'essai de #membres (OverOp-S) portait fin = 2026-08-16 18:21:03 et l'état suspendu. Quarante minutes après l'échéance, et après un balayage horaire qui a bien tourné à 19:00:00 :
| Attendu | Mesuré |
|---|---|
etat → fini | toujours suspendu |
motif_fin renseigné | vide |
updated_at postérieur | inchangé depuis le 15/08 19:00:00 |
| une ligne au journal des prêts | aucune — 12 lignes avant, 12 après |
⚠ Et le mécanisme SAIT le faire : IriX_mandat_log porte deux expirations réussies (fin | expire | échéance atteinte, les 07 et 12/08) — sur des mandats actifs. Ce n'est donc pas une fonction morte, c'est une porte qui ne s'ouvre que dans un cas.
La cause, exacte, dans ::soc::mandat::echus (IriX/cmds/irix-mandat.tcl) :
WHERE etat = 'actif' AND fin IS NOT NULL AND fin <= NOW()
Le balayage ne ramasse que les mandats actif. Un mandat suspendu dont l'échéance est passée n'est jamais sélectionné, donc jamais terminé : il reste indéfiniment en base, ni actif, ni fini.
⚠ Le même ordre de contrôles existe dans le verdict d'une demande d'action : suspendu est rendu avant que l'échéance soit examinée. Là c'est juste — qui demande une action veut savoir qu'elle est suspendue. Ici, non : le balayage ne répond à personne, il fait le ménage.
| ✅ CONSTATÉ FERMÉ | Le 23/08/2026, sur une trace du 17/08 : le prêt de #membres (OverOp-S), suspendu automatiquement le 15/08, échu le 16/08, a traversé son échéance sans bouger tant que l'ancien code tournait — puis s'est terminé au premier balayage horaire suivant le chargement du correctif, avec sa ligne au journal du robot et au journal des prêts. ⚠ La question à trancher l'était déjà : MANDATOFF ne retire pas le robot, donc le retrait après expiration est nécessaire. |
|---|
⚠⚠ LE DOUBLON QUE CE CONSTAT A TROUVÉ — CONSTATÉ FERMÉ À SON TOUR LE 23/08/2026 À 18:00:00. En lisant le journal pour fermer ce point, deux lignes fin sont apparues pour une seule fin : ids 15 (ok) et 16 (expire), même salon, même robot, même seconde. terminer journalise depuis le 14/08, et balayer journalisait encore de son côté pour marquer expire plutôt que ok — chacune avait raison séparément. Corrigé en 4.19.48 : balayer passe l'issue à terminer, qui écrit la ligne unique.
Constaté sur un prêt provoqué, pas sur du code relu. OverOp-T prêté à #!Overtchat! le 23/08 à 17:38:51 par la file IriX_botreq (demande #5, traite_par = essai-doublon-23-08), échéance reculée dans le passé, puis laissé au vrai balayage horaire de 18:00:00.
| Ancien code — 17/08 18:00:00 | Après le correctif — 23/08 18:00:00 | |
|---|---|---|
lignes fin au journal | 2 — ids 15 et 16 | 1 — id 20 |
| issues portées | ok, expire | expire |
⚠ Ce constat pouvait rougir, et c'est ce qui lui donne sa valeur : le monde contenait déjà le défaut. Les ids 15 et 16 sont la trace de l'ancien code sur exactement ce chemin — échéance atteinte, balayage horaire.
⚠ Il prouve plus que le compte : l'issue expire est bien remontée jusqu'à terminer. Une seule ligne portant ok aurait été un demi-succès — le doublon parti, mais le journal redevenu incapable de distinguer une fin par le temps d'une fin ordonnée par un opérateur.
⚠ Le seul geste artificiel est le recul de fin en base, pour simuler un jour écoulé. echus, balayer et terminer ont tourné par le vrai bind time - "00 * * * *", sans être touchés. Le robot est réellement entré (OverOp-T … joined #!Overtchat!, observé par IriX, pas l'ordre) et réellement sorti (left #!Overtchat! à 18:00:01). L'essai a été nettoyé : #!Overtchat! retiré à froid du chanfile d'OverOp-T, qui a repris son BOTSTAT sur le seul #!Bots! à 18:07:00.
⚠ Ne pas élargir la clause sans regarder terminer et le retrait du robot : le commentaire de balayer dit « la fin n'a pas pu s'écrire : on ne retire SURTOUT pas le robot ». L'inverse — retirer deux fois un robot déjà sorti — n'a pas été mesuré.
22. ✅✅ L'ASV — CONSTATÉ le 16/08/2026 à 16h, et le constat a trouvé une panne totale
Ouvert puis clos le 16/08/2026. Le point demandait d'ouvrir le chat dans un vrai navigateur. C'est fait, sur le site public. Et le premier essai n'a pas validé l'ASV : il a montré que plus personne ne pouvait entrer sur le chat.
⚠⚠ CE POINT EST LA DÉMONSTRATION DE CE QU'IL AFFIRMAIT. Il disait « 65 constats, 14 mutations, rien de tout cela n'est un constat en service ». Il avait raison au-delà de ce qu'il croyait : le navigateur envoyait son jeton anti-CSRF sous le nom csrf, quand le serveur le lit sous _csrf. Réponse 400 à chaque tentative, depuis la 4.18.0. Cinq constats de test-asv.php étaient au vert pendant toute la panne — ils vérifiaient que la route contient les bons appels, pas qu'elle réponde. Corrigé en 4.18.5, avec trois constats qui croisent désormais le nom lu par csrf_check() et ceux employés par chat.js.
Ce qui a été constaté en service, après correctif :
| Ce qui a été fait | Ce que le serveur a répondu |
|---|---|
| case décochée, 30 / Secret / Lyon | 200 — realname = Visiteur du site |
| case cochée, 24 / Femme / Lyon | 200 — realname = 24F Lyon |
| case cochée, 40 / Homme, saisi « saint etienne » | 200 — realname = 40H Saint-Etienne (la ville vient de la liste, pas de la frappe) |
| 15 ans | 400 — « L'accès au chat est réservé aux 16 ans et plus. » |
| ville inventée | 400 — « Ville inconnue — vérifiez l'orthographe, ou choisissez la commune la plus proche. » |
⚠ Le /WHOIS lui-même n'a pas été lu. Le realname composé par le serveur a été constaté à la source, mais personne n'est entré sur le réseau pour le voir affiché à un tiers — la connexion a été délibérément bloquée pendant l'essai. C'est le seul reliquat, et il ne demande qu'un /WHOIS par SerVuS un jour où il se connecte.
⚠ Le contrôle protège le formulaire, pas le port : qui ouvre le WebSocket lui-même, ou se connecte avec mIRC, ne passe pas par là. C'est cohérent avec l'arbitrage « webchat obligatoire, IRC facultatif », et c'est une limite connue.
⚠ L'ASV n'est pas stocké : il vit le temps de la connexion, dans le realname. La modération n'y a donc accès que si la personne publie. Le tableau de la charte le dit ainsi, et il dit vrai.
23. ✅✅ L'alerte critique — CLOSE le 16/08/2026 à 13:13:53, chemin complet parcouru
⚠ CONSTATÉ FERMÉ, et par la seule preuve que ce point acceptait : le chemin entier, d'un échec réel jusqu'à son rangement.
13:03:14 attempt OverOp-H « link » demandé par le panel
13:04:14 error OverOp-H le lien n'est toujours pas linked 60s plus tard (echecs=1)
13:06:14 error OverOp-H (echecs=2)
13:09:14 error OverOp-H (echecs=3)
13:09:14 [CRIT] ALERTE botnet : Le lien vers OverOp-H ne monte pas
→ IriX-sql.log ET IriX.log, ligne #1 en base, bandeau sur le tableau de bord
13:12:16 courriel envoyé à service.overtchat@free.fr
13:13:53 rangée — la ligne reste (compte, mail_at, acquit_par), le bandeau DISPARAÎT
⚠⚠ ET LE CONSTAT A TROUVÉ UN DÉFAUT QUE RIEN N'AURAIT MONTRÉ. Au premier essai, trois « lier » sur OverOp-H ont donné trois attempt, state=unlinked, echecs=0 et aucune alerte : on pouvait cliquer indéfiniment sans que rien ne le signale. La cause était écrite dans le code et jugée close — « le résultat de link ne prouve rien, Eggdrop rend 1 quand il a LANCÉ la tentative » — c'était juste, et incomplet : personne ne regardait ensuite si elle avait abouti. Corrigé en 4.18.3, et c'est ce correctif qui a permis le constat ci-dessus.
Un mécanisme d'alerte ne se vérifie qu'en le faisant échouer pour de vrai.
| Pour fermer | ✅ RIEN — CLOS le 16/08/2026 à 13:13:53. ⚠ Ce qui reste et n'appartient plus à ce point : le nombre d'appelants, suivi au point 24 — quatre à la fermeture de celui-ci, douze le soir même. ⚠ Cette ligne a annoncé « tout le reste échoue encore en silence » pendant une heure après que ce ne fut plus vrai : une phrase juste au moment où on l'écrit se périme comme les autres, et c'est un agent de relecture qui l'a trouvée, pas moi. |
|---|
24. ✅ L'alerte est branchée sur TREIZE points — chaîne constatée de bout en bout le 17/08/2026
Avancé le 17/08/2026 — les boucles qui mouraient en silence
Remesuré : 13 appels, 7 modules, 11 atteignables, 2 morts — les comptes du registre sont exacts et tiennent. ⚠ Une nuance de plus, non écrite : le
catchde l'IA (#7,scan-inbox) est atteignable, mais pas par le motif que son propre commentaire annonce —scan_inbox_passn'appelle quedb_sel/db_exec, qui ne lèvent jamais. Le cas d'usage documenté est mort ; seule une erreur Tcl ailleurs l'atteint.Ce qui a été corrigé — pas un des treize points, mais la famille voisine que ce point signale : les
catchnus. Trois réarmements de boucle avalaient leur échec.
Où Ce que le silence coûtait irix-botnet.tcl,cyclel'observation s'arrête pour de bon ; le panel continue d'afficher le dernier état comme s'il était frais irix-alerte.tcl,cycle_courriels⚠⚠ plus aucun courriel d'alerte ne part, et rien ne le dit irix-alerte.tcl,demarrer_courrielsle cycle ne démarre jamais ; l'appelant croyait le mécanisme en marche ⚠ Les deux
catchqui précèdent ce réarmement sont, eux, légitimes : ils protègent UN passage, celui qui échoue est perdu et le suivant aura lieu. Le troisième protège la SUITE — s'il échoue, il n'y a pas de suivant. La distinction est ce qui décide, pas la forme du code.⚠ Le module d'alerte ne s'alerte pas lui-même : bâtir l'alarme sur ce qu'elle doit surveiller ne prouverait rien. Les deux journaux, et rien d'autre. Ailleurs,
erreuret nonechec: l'événement n'arrive qu'une fois, un seuil de trois répétitions ne serait jamais atteint.Banc :
test-alerte.tclfait échouerutimer— seule façon d'atteindre cette branche — et exige le journal. ⚠ Il éprouve aussi le cas nominal, sans quoi le constat passerait au vert sur une boucle qui ne se réarme jamais. Mutation faite : lecatchnu remis, deux constats rougissent.Les deux branches mortes, tranchées le 17/08/2026
chanq/file-illisible— RETIRÉE. Lecatchentourait undb_sel, qui ne lève jamais. ⚠ Elle était même doublement inatteignable :db_ensure_table, en tête de la même procédure, est elle aussi bâtie surdb_selet fait sortir avant. Retirée plutôt que rendue vivante, parce que l'alerte existe déjà plus près de la cause :db_sellèvesql/sel:<message>depuis l'intérieur. Lui ajouter un moyen de distinguer « file vide » de « lecture échouée » — que rien dans le projet ne permet aujourd'hui — aurait bâti un second système d'alarme parallèle au premier pour dire la même chose. ⚠ Ce qu'on perd est la granularité, et c'est écrit dans le code : une alerte moins précise qui part vaut mieux qu'une alerte précise qui ne part jamais.
botreg/pool-arrete— CE N'ÉTAIT PAS UNE BRANCHE MORTE. L'instruction a montré que toute la chaîne productrice du pool n'est branchée nulle part. Le sujet dépasse ce point : voir le point 35, ouvert pour cela.⚠ Le motif est SYSTÉMIQUE, et le recensement du point le sous-estimait : quatre autres endroits répètent le même
catchinatteignable autour d'undb_sel—irix-modq.tcl(deux fois, dont une gardedb_scalar),IriX-svc.tcl, et::soc::admin::scanlui-même. Ceux-là ne portent qu'unputlog, donc un silence et non une fausse promesse ; ils restent à nettoyer. ⚠⚠ EtIriX/tests/test-autoconfig-relu.tclbouchonnedb_selen la faisant lever — un banc qui éprouve un contrat que la vraie procédure n'offre pas.Banc
test-branche-morte.tcl: il dérive le motif sur toutIriX/cmds/et exige zéro. ⚠ Il s'éprouve lui-même sur un corpus fabriqué — une vraie branche morte trouvée, et troiscatchlégitimes (exec,socket, sans alerte) laissés tranquilles. Un détecteur qui ne détecte rien a exactement l'allure d'un projet sain.✅ L'alerte constatée DE BOUT EN BOUT le 17/08/2026
Panne provoquée en production, avec l'accord de SerVuS, et réversible :
chmod a-wsurIriX/logs/archives, pluslast_rotateremis à NULL. ⚠ La restauration était posée dans untrap EXITavant le geste — une panne provoquée qu'on oublie de refermer est pire que pas d'essai.
Maillon Constat déclenchement 3 échecs de rotation à 30 s d'intervalle : 12:53:11, 12:53:41, 12:54:12 seuil l'alerte tombe au 3ᵉ, exactement — SOC ALERTE [CRIT] logrotà 12:54:12journal du bot IriX.log, la ligne nommant les deux fichiers et la cause (permission denied)base IriX_alertes#3,crit, ouverte,compte=1courriel parti à 12:58:58 vers service.overtchat@free.fr,IriX_mail_log#18 statutokpanel 1 critique ouverte → bandeau ; après rangement, 0 → plus de bandeau rangement par alerte_acquitter(), le chemin réel du bouton, pas unUPDATEà la main⚠ Aucune perte : l'échec survient à la copie, donc la troncature n'est jamais atteinte — les journaux gardent tout, et les 28 archives sont intactes. Restauration vérifiée après coup : droits
755,last_rotaterevenu à09:43:31.⚠ Le délai du courriel est de ~286 s, et c'est normal : le cycle d'envoi passe toutes les 300 s. Une alerte rangée dans cet intervalle ne part pas — c'est voulu, et c'est aussi pourquoi le rangement rapide n'est PAS un moyen fiable de faire taire un essai.
Ce qui n'a pas été provoqué, et pourquoi
Deux familles sur quatre sont constatées en vrai : balayage (logrot, aujourd'hui) et lien (botnet, le 16/08). Les deux autres —
uline/socketetmandat/observation-morte— demanderaient de couper un lien en service ou de vider une table de production.⚠ Et cela n'apprendrait rien de plus sur la chaîne : elle est unique, et elle vient d'être éprouvée bout à bout. Ce qui diffère d'une famille à l'autre est le point d'appel, et ces points-là sont désormais gardés par dérivation :
test-branche-morte.tclexige qu'aucun ne soit enfermé dans uncatchinatteignable. Provoquer une panne réelle pour éprouver du code déjà dérivé serait payer cher une information qu'on a.
Ouvert le 16/08/2026 en fermant le 23, et avancé le même jour : de 4 points à 13, de 2 modules appelants à 7.
⚠⚠ CE TITRE DISAIT « DOUZE ». C'ÉTAIT FAUX LE JOUR MÊME OÙ IL A ÉTÉ ÉCRIT — mesuré le 16/08/2026 en dérivant les appels depuis les sources, et trouvé deux fois indépendamment. Il y a 13 appels à ::soc::alerte::echec / ::soc::alerte::erreur dans IriX/cmds/. Celui qui manque à toutes les énumérations est IriX/cmds/irix-chanq.tcl (file-illisible), livré par le même commit que les autres. Ce n'est donc pas une dérive dans le temps : c'est un compte fait à la main, faux dès l'écriture. Le compte de modules (7) est juste, lui, parce qu'il a été dérivé. Un nombre écrit à la main ne survit pas à la séance qui l'écrit.
⚠⚠ ET DEUX DES TREIZE NE PEUVENT PAS SE DÉCLENCHER, mesuré le 16/08 :
chanq/file-illisibleest dans une branche morte. Soncatchentoure::soc::db_sel, qui ne lève jamais — elle attrape l'erreur, la journalise et rend{}. C'est exactement le piège que ce point documente pourdb_exec, reproduit avecdb_seldans le commit même qui le documentait.botreg/pool-arreteest dans::soc::botreg::demander_robot, qui n'est appelée nulle part en production — deux occurrences dans tout le dépôt : sa définition, et un banc.
Restent 11 points atteignables, dont 2 constatés (la rotation des journaux ci-dessous, et le lien de botnet du point 23).
✅ Ce qui parle désormais (4.18.4) : le dialecte non implémenté et les SID épuisés (erreur, permanents), la socket qui refuse et le lien fantôme (echec), les trois installations de robot ratées qui arrêtent le service (erreur), le contrôle des prêts qui tourne à vide (erreur), la rotation des journaux qui boucle (echec), le moteur de modération qui tourne à vide (echec).
⚠⚠ ET DEUX ENDROITS OÙ L'ALERTE NE DOIT PAS SE LEVER — ils valent plus que dix branchements. Une alerte qui se lève au mauvais moment apprend à ignorer le bandeau, et l'on perd alors toutes les autres. (1) Une table absente ne lève rien : au premier démarrage elles n'existent pas encore, et sans cette exception toute installation neuve crierait dès sa première minute. (2) L'alerte n'est pas branchée sur set_error, l'entonnoir des sockets, parce qu'il porte aussi « Socket désactivé » — le refus légitime de connecter un lien volontairement coupé.
✅ UN DES HUIT EST CONSTATÉ — la rotation des journaux, le 16/08/2026 à 14:59:23. Panne provoquée volontairement (droits retirés au répertoire d'archives, date de rotation reculée), et tout restauré ensuite :
14:58:23 LOGROT: rotation incomplète — permission denied (1er échec)
14:58:53 LOGROT: rotation incomplète (2e)
14:59:23 LOGROT: rotation incomplète (3e)
14:59:23 SOC ALERTE [CRIT] logrot : La rotation des journaux échoue
⚠ Et le comportement d'après compte autant : les échecs de 14:59:53 et 15:00:23 n'ont pas relevé d'alerte. Le compteur repart bien de zéro après avoir levé — sans quoi la déduplication en base aurait masqué le défaut derrière un compteur qui monte.
⚠ Les sept autres ne sont pas constatés : écrits, éprouvés au banc, jamais vus agir.
Le recensement d'origine portait une trentaine de candidats ; les autres restent : le lien fantôme côté mark_ghost est fait, mais pas les échecs de burst, ni les suspensions non écrites, ni les cinq catch nus du balayage horaire (rate_purge, mandat::balayer, bascule_charge, signaler_non_servis, suspendre_non_servis) où une erreur Tcl disparaît sans même un putlog.
⚠⚠ DEUX PIÈGES MESURÉS, à connaître avant de brancher quoi que ce soit :
- Les
catch { ::soc::db_exec … } erront une branche MORTE.db_execne lève jamais : il journalise et rend 0. Une dizaine de sites du dépôt entourent undb_execd'uncatchdont la branche d'erreur est inatteignable — y poser une alerte serait écrire dans du code mort. ⚠ À ne pas confondre avecdb_execlui-même, qui alerte bien, depuis l'intérieur de sa propre branche d'erreur. Ce sont lescatchque d'AUTRES procédures posent autour d'un appel àdb_execqui sont morts, parce quedb_execne lève jamais. La nuance a déjà induit un relecteur en erreur. alerte::echecest INERTE sur ce qui tourne une fois par heure. Sa fenêtre est d'une heure
⏪ CE QUI SUIT EST L'ÉTAT D'AVANT LA FERMETURE — PÉRIMÉ, gardé pour la trace. Le critère décrit ci-dessous a été tenu le 17/08/2026 — voir le constat en tête de point. ⚠ Et le critère lui-même est caduc, pas seulement atteint : deux familles ont été constatées, et il a été assumé de ne pas provoquer les deux autres — un tiers pourrait noyer le bandeau à volonté, et le volume rendrait la page illisible. ⚠ Ne pas le relire comme du travail à faire. Un encadré « Pour fermer » périmé sous un titre ✅ est exactement ce qui fait rouvrir un point déjà clos.
et son seuil de trois : un mécanisme horaire ou quotidien n'atteindra jamais trois. Pour tout ce qui vit dans scan::hourly ou daily, il faut alerte::erreur — ou une clé qui agrège plusieurs cibles dans le même passage.
| Pour fermer | Constater au moins une alerte réelle par famille — un socket, un prêt, un balayage —, et brancher le reste du recensement. ⚠ Ne PAS brancher ce qui est déclenchable depuis IRC par un tiers (BOTREQ mal formé, mauvais secret) : ce sont des refus corrects, et un tiers pourrait noyer le bandeau à volonté. ⚠ Ni ce qui tourne à chaque message ou à chaque salon : le volume rendrait la page illisible. ⚠ Et le plus simple à constater est le lien fantôme ou la rotation : couper les droits d'écriture sur le répertoire des archives suffit à faire échouer logrot trois fois, donc en 90 secondes. |
|---|
23. L'état d'avant, gardé pour mémoire
Ouverte le 16/08/2026. ✅ La partie visible est faite en 4.18.2 — bandeau sur toutes les pages, page de lecture, acquittement au niveau op, pastille de menu, courriel par balayage borné. Les trois destinations tranchées par SerVuS sont couvertes.
⚠ Et le point reste ROUGE, parce que rien n'a été constaté. Aucune alerte réelle n'a jamais été levée : le chemin complet — un échec répété, une ligne en base, un bandeau, un courriel, un acquittement — n'a jamais été parcouru une seule fois.
⚠⚠ Un seul appelant. Le module ne couvre que le refus de botnet. Poser l'alerte n'a pas protégé du motif « à la jointure » : il en a créé un de plus. Chaque mécanisme qui échoue en silence aujourd'hui continuera de le faire tant qu'il n'appellera pas ::soc::alerte::echec.
⚠ La page dit elle-même ce qu'elle ne peut pas dire : « une page vide ne veut pas dire que tout va bien ». Sans cet encart, elle deviendrait la nouvelle section 🔴 vide du registre — rassurante et fausse.
| Pour fermer | Voir le chemin complet une fois. Le plus simple : recharger IriX, puis laisser un refus de botnet se produire trois fois (par exemple en demandant « lier » sur un pair désactivé). Attendu : une ligne dans <bot>_alertes, le bandeau rouge sur toutes les pages du panel, un courriel dans les cinq minutes, et le bandeau qui disparaît au clic sur « Ranger ». ⚠ Vérifier aussi qu'un second courriel ne part PAS pour la même clé. ⚠ Et compter les appelants : un seul aujourd'hui. |
|---|
Aucun autre au 16 août 2026, 11:11:06. Les points 20 et 21 ont été constatés fermés à cette minute — le panel a monté un lien de botnet, ce qu'il n'avait jamais réussi à faire depuis que ce code existe. Ils rejoignent les travaux ci-dessous, où ils gardent tout leur texte.
⚠ Et il faut redire ce que la veille disait déjà deux fois : « ce qui vide cette section, c'est parfois seulement de ne pas avoir regardé ». Le 15/08 elle était vide à 19:06 — six actions du panel étaient déjà mortes à cet instant, et rien ne le disait. Ce n'est pas un banc qui les a trouvées : c'est un utilisateur qui a cliqué.
🟠 Travaux à effectuer
54. ✅ Le site annonçait une version d'OverScript qu'il ne DISTRIBUAIT pas — CONSTATÉ FERMÉ le 26/08/2026
| ✅ CONSTATÉ FERMÉ | Le 26/08/2026, sur la production : /dl/overscript rend 90 287 977 octets, l'empreinte téléchargée est identique à celle déposée, et l'en-tête du fichier est bien MZ. Le fichier annoncé est donc réellement là. |
|---|
Le circuit, en entier :
| Pièce | Où |
|---|---|
| le binaire | Web/.run/overscript-installateur.exe — hors dépôt, comme site.tar.gz |
| le journal des versions | Web/.run/overscript-changelog.md — déposé, jamais recopié ici |
| l'entrée | distrib_inventaire(), clé overscript |
| la page | ?p=versions&pour=overscript — celle où atterrit le bouton du client |
| le constat | Web/tests/test-overscript.php, 24 constats |
⚠⚠ LE NOM SERVI EST STABLE, SANS NUMÉRO. Un nom versionné casserait tous les liens et tous les signets à chaque publication — même leçon que le raccourci du client, qui pointe vers une copie à nom stable plutôt que vers dist/.
⚠ Le journal n'est PAS recopié dans ce dépôt. Une copie suivie par git se périmerait au premier écart entre les deux dépôts — le défaut de famille du projet. La page lit ce qui a réellement été publié, et le dit quand il manque au lieu d'afficher une section vide.
⚠⚠ Deux choses qui ont menti, et une qui ne pouvait pas rougir
1. WEB.md et le CHANGELOG du client affirmaient tous deux que le journal « est publié avec chaque version ». Il ne l'était nulle part : le mot CHANGELOG n'apparaissait ni dans Web/inc/, ni dans Config/overscript.json. Une affirmation d'état en français, dans un fichier qui part chez les utilisateurs. Les deux sont corrigés.
2. Mon premier constat était faux dans l'autre sens : il exigeait le fichier dès que l'inventaire le nommait — or il le nomme toujours, c'est une constante. Il rougissait donc sur tout clone de développement, où le binaire n'a aucune raison d'être. ⚠ Un banc rouge en permanence finit par ne plus être entendu. La condition porte désormais sur le manifeste, qui ne liste que ce qui existe : ce que le site publie doit être là, et être ce qu'il dit.
3. Et md_render rend une LISTE [html, toc], pas un tableau associatif. La première écriture de la page lisait $rendu['html'] : elle aurait affiché « journal indisponible » sans qu'il manque quoi que ce soit.
⚠ Ce que les bancs du site ont attrapé, et qui ne se voyait pas
Deux échecs après la mise en production, tous deux de vrais défauts visuels :
test-vues-site.php— le bloc.mdne vivait que dans le<style>deversions.php: le journal d'OverScript s'affichait sans aucune mise en forme. Et le sélecteur des deux onglets était défini dans la page OverScript alors qu'il est employé des deux côtés — les boutons de la page générale étaient donc sans style. Les deux blocs vivent maintenant danssite.css.test-nav-site.php—versions-overscript.phpétait vue « inatteignable » : elle est incluse, pas routée. ⚠ Plutôt que de l'ajouter à la liste d'exceptions — qui dit « ne regarde pas » —, le banc relève désormais les inclusions et le vérifie. Avec sa garde : un relevé qui ne trouve rien doit le dire.
<details><summary>L'énoncé d'origine du 25/08, gardé parce qu'il explique la décision</summary>
Le canal de mise à jour d'OverScript est branché, et son dernier maillon manque. Demandé par SerVuS le 25/08/2026 : « ajouter un numéro de version sur OverScript pour voir les modifs, mettre un bouton mise à jour, et faire le lien via GitHub avec détection automatique ».
⚠⚠ GITHUB A ÉTÉ ÉCARTÉ SUR MESURE, PAS SUR PRÉFÉRENCE. api.github.com répond 404 en anonyme sur les trois dépôts du projet — y compris Overtchat, où un push venait de réussir. Ils sont privés, et GitHub rend la même chose pour « privé » et pour « inexistant ». Un client installé chez un tiers n'a pas de jeton, et on ne peut pas en embarquer un dans un binaire distribué : il serait lisible par quiconque ouvre le paquet. C'est exactement la raison pour laquelle l'installateur du projet tire déjà ses composants du site. SerVuS a tranché pour le site.
Ce qui est fait et constaté :
| Pièce | Où | Constaté |
|---|---|---|
| la version au runtime | src/main/main.js → preload → app.js | ✅ bancs/rendu.js la voit traverser jusqu'à l'écran, sous le vrai binaire |
| le journal des versions | CHANGELOG.md (OverScript) | ✅ bancs/version.test.js |
| la source côté site | Config/overscript.json | ✅ Web/tests/test-overscript.php |
| la route | ?p=overscript | ✅ HTTP 200 en production |
| la détection | src/main/maj.js | ✅ bancs/maj.test.js, plus 8 blessures de mutation |
⚠⚠ CE QUI MANQUE — ET LE BOUTON MÈNE DONC À UNE PAGE OÙ LE FICHIER N'EST PAS. Mesuré le 25/08/2026 : le mot « OverScript » n'apparaît ni dans distrib_inventaire(), ni sur ?p=telechargement. Le client dira correctement « une version plus récente existe », ouvrira la page de téléchargement du site — et l'utilisateur n'y trouvera rien.
⚠ C'est le défaut de famille du projet, pris à temps : ça marche ici, et rien n'arrive chez le tiers. La différence avec les quatre cas du 16/08 est qu'il est nommé avant d'avoir menti à quelqu'un.
| Pour fermer | Une décision de SerVuS d'abord : distribuer depuis ce site un exécutable d'une centaine de mégaoctets, et depuis quel emplacement — ⚠ aucun binaire n'est suivi par git dans l'un ou l'autre dépôt, donc le fichier vivra hors dépôt, comme Bootstrap/overtchat-setup et l'archive du site. Ensuite : l'entrée dans distrib_inventaire(), la ligne sur ?p=telechargement, et un constat qui télécharge réellement le fichier annoncé — un inventaire qui nomme un fichier absent est précisément ce que distrib_manifeste() a déjà connu. |
|---|
⚠ La procédure de publication est écrite : Markdown/WEB.md, section « Publier une version d'OverScript ». Elle porte le seul geste du circuit qui ne dérive de rien — les deux dépôts étant séparés, le numéro se recopie à la main dans Config/overscript.json.
</details>
53. ✅ Un fichier d'état recopiait le numéro de version, et le contrôle ne le voyait QUE tant qu'il était juste — CONSTATÉ FERMÉ le 26/08/2026
| ✅ CONSTATÉ FERMÉ | Le 26/08/2026 à 12:14, sur la production, et par le chemin du code — une commande CLI ordinaire, qui passe par _write_default_config puis _merge_configs comme n'importe quel passage de l'installateur. |
|---|
AVANT state.json version = 4.19.30 APRÈS state.json version = (absente)
state.default.json = 4.23.2 state.default.json = (absente)
Et les trois choses qui comptent, mesurées après :
- l'écran d'accueil affiche 4.23.2, égal au
VERSIONdu dépôt — la dérivation marche ; state.jsonreste valide : la migration n'a emporté que sa cible ;test-json.phprepasse au vert de lui-même — 18 constats, 0 échec. On n'a rien fait taire.
⚠⚠ Le fichier n'a PAS été purgé à la main, et c'était la condition écrite de ce point : purger aurait éteint le symptôme sans jamais éprouver la migration, et le prochain tiers aurait retrouvé le défaut entier. Sauvegarde avant le geste : /home/servus/sauvegarde-state-20260826-121446.
Campagne complète après coup : 107 bancs au vert, 5009 constats.
Trouvé le 25/08/2026, en posant les empreintes de référence avec SerVuS. test-json.php a rougi sur un constat qui dormait depuis longtemps :
ÉCHEC aucun JSON ne recopie le numéro du projet
obtenu : ["Runtime/state.default.json : version"]
Deux fichiers, deux états — et c'est le second qui instruit :
| Fichier | version | |
|---|---|---|
Runtime/state.default.json | 4.23.2 | régénéré à l'instant, donc juste |
Runtime/state.json (celui en service) | 4.19.30 | périmé de plusieurs branches |
⚠⚠ ET LE BANC NE SIGNALE QUE LE PREMIER. Sa règle est $val === $VERSION : il ne dénonce un numéro recopié que s'il est encore exact. Dès que le chiffre se périme — c'est-à-dire dès qu'il devient un problème — le contrôle se tait.
C'est le motif « garder la porte en regardant la serrure », et sous sa forme la plus retorse : le banc est vert pendant toute la durée où le défaut nuit, et rouge à la seule seconde où il ne nuit pas encore. state.json porte 4.19.30 depuis des semaines sans que rien ne l'ait dit.
⚠ Ce n'est pas un défaut de discipline mais de MÉCANISME : ce dépôt a pour règle qu'un chiffre qui se périme se retire au profit de la commande qui le mesure. Ici, c'est l'installateur qui écrit le numéro, et aucun humain ne le recopie.
⚠ Les deux fichiers sont hors dépôt — ils ne partent chez personne. Le défaut est donc local à chaque installation, ce qui le rend d'autant plus discret : il ne se voit pas dans un diff.
✅ La moitié (2) — le CONTRÔLE — est réparée le 26/08/2026
Le banc dénonçait le fichier qui avait raison et laissait passer celui qui mentait :
| Fichier | version | avant | après |
|---|---|---|---|
Runtime/state.default.json | 4.23.2 (juste) | dénoncé | dénoncé |
Runtime/state.json | 4.19.30 (périmé) | ⚠ invisible | dénoncé |
La règle retenue, et pourquoi celle-là. Une valeur qui est ou a été un numéro de ce projet — l'historique de VERSION.md la donne. ⚠ Appliquée aux seuls fichiers d'état (Runtime/state*) ; partout ailleurs la règle d'origine tient, l'égalité avec VERSION.
⚠⚠ La première écriture appliquait l'historique PARTOUT, et elle était fausse. Elle a dénoncé Config/servers.json : servers.inspircd.version = 4.11.0 — une vraie version d'InspIRCd, qui se trouve être aussi une des nôtres. La collision est structurelle : rien n'empêche un logiciel tiers de porter un numéro que nous avons porté.
⚠ Elle n'était pas visible depuis le dépôt. Config/servers.json n'est pas suivi par git, et le clone Windows ne déclare même pas inspircd. Il a fallu éprouver le contrôle sur la production pour la voir. Mesurer là où vit la chose mesurée.
La passe de mutation que ce point exigeait est écrite — quatre cas qui cassent la règle exprès, dans les deux sens, et qui appellent la décision que le balayage emploie réellement, pas une copie :
- un numéro périmé dans un fichier d'état → dénoncé ;
- le numéro courant dans un fichier d'état → dénoncé ;
- ⚠ le même numéro périmé dans un fichier de composants → non dénoncé (c'est le fichier qui décide, pas la valeur) ;
- le numéro courant dans
Config/→ dénoncé, la garde d'origine tient.
⚠ Le numéro périmé y est dérivé de l'historique, jamais écrit à la main : un numéro en dur se périme, et ce banc parle précisément de numéros périmés. Un cinquième constat refuse que le balayage travaille sur un historique vide — muet ressemble à vert.
test-json.php : 11 → 17 constats.
🟡 La moitié (1) — l'installateur — écrite et éprouvée le 26/08/2026
Trois points de contact, tous mesurés avant d'y toucher :
| Où | Ce qui change |
|---|---|
_write_default_config | --arg version et version: $version retirés — la clé ne s'écrit plus |
_merge_configs | \| del(.version) — la migration, seul endroit où tout état déjà en service repasse |
| l'en-tête du menu | _version_installee(), qui dérive au lieu de lire l'état |
⚠⚠ LA DÉRIVATION LIT $BASE_DIR/VERSION, PAS $VERSION. $VERSION vient du chemin du script : chez un tiers, l'installateur se télécharge seul, n'a aucun VERSION à côté de lui et retombe sur son repli 4.0.0. Afficher $VERSION aurait remplacé un mensonge par un autre — et par le pire des deux, puisqu'il aurait été juste sur ce poste-ci.
⚠ Un piège qui ne se voit pas : _jq_read ne replie sur N/A que si jq sort en code non nul, or une clé absente fait sortir jq en 0 en imprimant null. Retirer la clé sans toucher la ligne de lecture aurait affiché « null » à l'écran d'accueil.
⚠ Et une erreur prise en écrivant : le gabarit jq est porté par une chaîne à quotes simples, et le script l'écrit noir sur blanc deux lignes plus haut. Un commentaire contenant des apostrophes l'a refermée — bash -n a rougi sur du jq parfaitement valide, quarante lignes plus bas.
Banc neuf : Bootstrap/tests/test-version-etat.sh, 17 vérifications, sur le modèle d'extraction de test-empreintes.sh — l'installateur finit par main "$@" sans garde, donc le sourcer le lancerait. ⚠ Il vérifie que chaque fonction a bien été extraite : sans cela un renommage laisserait un fichier vide et tout passerait au vert.
Passe de mutation — trois blessures, trois rouges :
| Blessure posée | Ce qui rougit |
|---|---|
| la migration ne purge plus | la clé périmée reste dans l'état (2 constats) |
| l'en-tête relit la version dans l'état | la dérivation n'est plus appelée (2 constats) |
la fonction rend $VERSION | les 5 constats de dérivation, dont celui du tiers |
✅ Le constat, pris le 26/08/2026
⚠ Le banc restera ROUGE tant qu'elle n'est pas faite, et c'est voulu : il dénonce le vrai défaut. Mais un banc rouge en permanence finit par ne plus être entendu — ce projet l'a déjà payé.
Mesuré le 26/08/2026, ce qui lève la précaution « vérifier d'abord qui lit cette clé » :
| Fichier | Lecteurs de sa clé version |
|---|---|
Runtime/state.default.json | aucun |
Runtime/state.json | un seul — Bootstrap/overtchat-setup.sh:2106, l'en-tête du menu |
⚠ Et la cause du gel est nommée : ce n'est pas un oubli d'écriture mais _merge_configs (overtchat-setup.sh:1838), dont la branche else . end conserve toute valeur non nulle et non-objet. state.default.json avance à chaque passage, state.json reste figé à sa première écriture. C'est la sémantique voulue de deep_merge, pas une dérive.
⚠ La fonction écrivaine s'appelle _write_default_config (ligne 1680) — le nom _init_default employé plus haut dans ce point n'existe pas dans le script.
Runtime/state.json perdre sa clé version par le chemin du code, sur la production, et test-json.php repasser au vert de lui-même.VERSION et Bootstrap/overtchat-setup.sh sont bien tous deux dans l'inventaire du site, vérifié. |
|---|
<details><summary>L'énoncé d'origine du 25/08, gardé parce qu'il explique la décision</summary>
| Pour fermer (état du 25/08) | Deux choses, et la seconde compte plus que la première. (1) Que l'installateur cesse d'écrire un numéro de version dans un fichier d'état — ou, s'il en a besoin, qu'il le dérive de VERSION à la lecture au lieu de le figer à l'écriture. ⚠ Vérifier d'abord qui lit cette clé : state.default.json sert de référence de réinitialisation, sa version dit peut-être de quelle version vient ce défaut, et la retirer sans regarder casserait ce sens-là. (2) ⚠⚠ Réparer le CONTRÔLE, qui est le vrai sujet : test-json.php doit dénoncer une clé version figée quelle que soit sa valeur, et non seulement quand elle égale la version courante. Tant qu'il compare à l'égalité, il restera vert précisément pendant que le défaut nuit. La mutation qui le prouve est facile et doit être écrite : poser un numéro périmé dans un JSON d'état, et vérifier que le banc rougit — aujourd'hui il ne rougirait pas. ⚠ Ne PAS « corriger » en excluant ces fichiers du balayage : ce serait faire taire le seul contrôle qui ait parlé. |
|---|
</details>
52. ✅ Le parcours d'inscription est à repenser en entier — DEMANDÉ par SerVuS le 25/08/2026, CONSTATÉ FERMÉ le 27/08/2026
Sa demande, dans ses termes : pouvoir réserver un pseudo depuis le site sans être connecté en même temps, puis disposer de 24 h pour venir le confirmer — par IDENTIFY sur IRC, ou depuis le chat, ou par un formulaire à côté du chat qui enverrait la commande au service. Et son constat de départ : « un truc qui n'est pas logique entre Avant de commencer et Créer le compte ». Il a raison, et le défaut est plus large qu'il n'y paraît.
Ce que le parcours fait AUJOURD'HUI — mesuré, pas lu
⚠⚠ Il y a TROIS portes d'inscription, pas une. Toute refonte qui n'en verrait qu'une en casserait deux.
| Porte | Preuve exigée | Compte créé | irc_verified |
|---|---|---|---|
?p=register | un code renvoyé plus tard sur IRC | oui, tout de suite | non |
?p=reservation (depuis le chat) | le mot de passe réseau, comparé immédiatement | oui | oui |
Tools/panel-account.php | aucune — c'est la machine | oui | oui |
L'incohérence que SerVuS signale, précisément. Le cadre « Avant de commencer » exige que le pseudo soit déjà enregistré sur le réseau. Le bouton, lui, crée immédiatement un compte sans qu'aucune preuve n'ait été faite, dans un état qui n'ouvre rien — et qui confisque le pseudo et l'adresse, tous deux UNIQUE. L'ordre est inversé : le site enregistre d'abord et vérifie peut-être jamais. ⚠ Et la page ne donne aucun moyen de faire le préalable qu'elle exige : pour envoyer REGISTER, il faut être sur IRC — donc au chat, où l'on n'a plus besoin de cette page.
Ce que la mesure a trouvé en plus — et qui décide de la refonte
- ⚠⚠ Rien ne purge un compte non vérifié. Jamais. Aucun
DELETEsurweb_usersn'existe dans le dépôt — ni PHP, ni Tcl, ni SQL, ni cron. Un compte abandonné garde le pseudo et l'adresse à vie, etreset.phprefuse toute récupération tant queirc_verifiedvautnon: ni entrée, ni sortie, ni libération. Le « délai de 24 h » que SerVuS demande n'a donc pas d'existence — il est à créer, et c'est lui qui rend le reste tenable. - ⚠⚠ Les deux portes se contredisent sur les mots de passe. La page promet : « le mot de passe du site est distinct de celui du réseau, ne réutilisez pas le même ». Le chat, lui, envoie
REGISTER <p>au service puis poste le même<p>vers?p=reservation, qui en fait le mot de passe du site. Deux doctrines opposées dans le même site, et aucun code n'arbitre. VERIFYne vérifie pas l'identification : la branche ne lit niusers.authni aucun grade. Ce qui referme la fenêtre en pratique estnickguard— un interrupteur en base, avec 60 s de grâce. Une garde de protocole s'appuie sur un réglage d'exploitation.- Deux politiques de mot de passe qui ne se parlent pas : le site impose 12 caractères, le
REGISTERd'IRC n'impose aucune longueur. Or la porte du chat fait des deux le même secret. REGISTERrenvoie l'utilisateur vers la page Inscription pour y poser son adresse — or cette page n'écrit jamaisIriX_users.email, seulementweb_users.email. Sans adresse côté réseau, la réservation ne peut jamais expirer. Mesuré en prod : les deux seuls pseudos enregistrés sont sans adresse.- Le lien entre les deux mondes est une chaîne comparée avec deux règles de casse :
web_usersest enutf8mb4_unicode_ci, donc insensible ; le Tcl compare avec la casse ailleurs. C'est le piège connu du projet, posé ici sur le lien qui porte toute la preuve. - ⚠ Aucun banc ne couvre le cycle inscription → code →
VERIFY→irc_verified. Casserregister_store,register_resendou la brancheVERIFYne ferait rougir personne. ⚠ EtIriX/tests/test-register.tclne teste pas l'inscription : il éprouve l'enrôlement de l'owner Eggdrop. Piège de nom.
⚠⚠ LE POINT DE SÉCURITÉ, à trancher AVANT d'écrire quoi que ce soit
SerVuS propose « un formulaire à côté du chat qui envoie la commande au service avec le mot de passe saisi ». Deux implémentations se ressemblent et n'ont rien à voir :
- dans le NAVIGATEUR — le JavaScript du chat envoie
IDENTIFYsur le WebSocket, comme n'importe quel client IRC. Le secret ne touche jamais le serveur web ni ses journaux. Le transport est déjà chiffré de bout en bout (webchat_url()rendnullhors HTTPS, donc un chat en clair est impossible), et le mécanisme existe déjà sous trois formes ; - par le SITE — formulaire → POST → PHP → IRC. Le serveur web voit le mot de passe réseau en clair, il peut finir dans un journal, et cela contredit frontalement ce que la page promet. ⚠ C'est ce que fait déjà
?p=reservation, et c'est le défaut n° 2 ci-dessus.
Recommandation : la première. Elle ne coûte presque rien — le chemin est écrit — et elle est la seule qui laisse la promesse de la page intacte.
✅ LA DÉCISION EST PRISE — 27/08/2026
La confirmation passera par le NAVIGATEUR : le JS enverra
IDENTIFYsur le WebSocket déjà ouvert, et le mot de passe réseau ne touchera jamais PHP. C'est la seule option qui laisse intacte la promesse affichée sur la page d'inscription. ⚠ Conséquence à assumer :?p=reservationfait aujourd'hui l'inverse — elle POSTe le mot de passe réseau vers PHP, le compare en base, et s'en sert comme mot de passe du site. C'est le chantier (2).✅ (1) L'EXPIRATION EST FAITE — 27/08/2026
::soc::webexpire, dansIriX-sql.tcl— aucun fichier neuf, donc aucun manifeste à toucher et aucun risque de chargement. Branchée au scan HORAIRE, pas au quotidien : l'échéance annoncée est 24 h, et un balayage quotidien donnerait de 24 à 48 h selon l'heure d'inscription.Ce qui manquait vraiment : aucun
DELETEsurweb_usersn'existait dans tout le dépôt. Un compte non vérifié n'a ni entrée ni sortie —auth.phprefuse de l'ouvrir,reset.phprefuse d'en récupérer le mot de passe — mais il confisque un pseudo et une adresse, tous deuxUNIQUE. Etweb_tokensn'était purgée par rien.⚠ La garde de sûreté :
irc_verified='non' AND level = 0, reposée à l'identique dans leSELECTet dans leDELETE. Un compte à grade n'est jamais touché.⚠ La purge est INERTE aujourd'hui — aucun compte non vérifié en base. Sans les deux comptages avant/après, rien ne distinguerait « rien à purger » de « refusé en silence », peut-être pendant des mois. Le banc fabrique donc lui-même les comptes que le monde réel ne contient pas.
⚠
RESTE UN GESTE : le code est en production mais IriX ne l'a pas rechargé.✅ GESTE FAIT ET CONSTATÉ le 27/08/2026. IriX a redémarré à 11:59:05 — après le commitf51ab05(11:33) et après l'écriture du fichier (11:58:47) — et le balayage horaire a tourné au premier passage, à 12:00:00 : « 1 inscription(s) jamais confirmée(s) retirée(s) après 24 h, 0 en attente » et « 2 jeton(s) mort(s) retiré(s), 0 gardé(s) ». ⚠ La trace est dansIriX/cmds/IriX-sql.log— ni dansjournalctl -u irix, ni dansIriX/runtime/IriX.log.Éprouvé :
IriX/tests/test-webexpire.tcl, 31 constats, 10 blessures sans trou.⚠⚠ Ce que la mutation a trouvé dans les BANCS — trois trous
Trou Ce qui le rendait invisible « la clause est reposée dans le DELETE » restait vert quand on la retirait le constat cherchait la clause dans tout ce qui était émis, or les deux SELECT COUNTla portent aussi — motif trop largetest-charte-retention.phpétait complice de son propre motifson contrôle « aucune purge n'échappe à la charte » énumérait ce que son motif avait su voir, et ce motif ne connaissait que INTERVAL … DAY. Une purge enHOURlui échappait — la plus grave, la seule qui efface un compte. Le rendre aveugle ne le faisait pas rougir : il le rendait muetl'unité n'était comparée par personne INTERVAL 24 HOUR→INTERVAL 24 DAYmultipliait le délai par 24, et la charte disait toujours « 24 »Trois contrôles ajoutés au banc de la charte : le motif accepte
HOURetMINUTE, tout ce que la charte annonce doit être vu par le motif, et tous lesDELETEdu Tcl sont énumérés sans passer par les durées. ⚠ Ce dernier a trouvé, le jour même, une table que personne n'avait déclarée :auto_pubs. Il passe de 19 à 30 constats.La charte publique porte les deux lignes correspondantes, écrites en même temps que la purge — pas après.
✅ (2) LA DOCTRINE DES SECRETS EST POSÉE — 27/08/2026
Un seul endroit —
Web/inc/doctrine-secrets.php— et les quatre écrivains la suivent.Deux règles. Le mot de passe du réseau (
IriX_users.password, SHA2, posé par le Tcl) et celui du site (web_users.pass_hash, bcrypt) sont distincts, et aucun code ne dérive l'un de l'autre. Et le secret du réseau ne traverse jamais PHP.Ce qui les défaisait :
?p=reservationrecevait le mot de passe réseau en clair dans un POST, le comparait en base, et s'en servait comme mot de passe du site. Une seule porte, les deux règles cassées. Pire : en cas d'échec, le JS rejouait le POST jusqu'à trois fois. Pendant ce temps la page d'inscription promettait, en toutes lettres, que les deux mots de passe étaient distincts.Ce qui remplace la preuve : une preuve qui se lit au lieu d'un secret qui se transmet — le pseudo est-il identifié, et vient-il de l'être ? Fenêtre de 5 minutes, calculée en SQL : l'heure de la base et celle du serveur web peuvent différer, et c'est la base qui a écrit
last_auth.⚠⚠ Ce que cette preuve ne couvre pas, et c'est écrit dans la doctrine : elle ne relie pas l'identification à cette session. Quelqu'un qui connaîtrait le pseudo et POSTerait dans la fenêtre passerait. Elle reste très supérieure à ce qu'elle remplace, mais ce n'est pas une session — le cran au-dessus est un jeton émis par le bot, et il appartient au (3).
La longueur minimale était décorative. Le site exigeait 12 caractères ; le service Tcl n'imposait rien. La page refusait « azerty »,
/msg OverNick REGISTER azertyl'acceptait — et la règle la plus faible gagne toujours. Une seule constante désormais, croisée par le banc.Le chat suit le même parcours : deux champs distincts, un refus explicite si l'on tape le même secret dans les deux, puis
REGISTER,IDENTIFY, et enfin le POST du seul secret du site.⚠ UNE EXCEPTION NOMMÉE reste, et le banc la nomme :
Web/inc/ircauth.phpdépose le SHA-256 du mot de passe réseau dans_web_authq. Le site voit donc encore ce secret sur ce chemin-là. À traiter au (3).Éprouvé :
Web/tests/test-doctrine-secrets.php, 33 constats, 14 blessures sans trou.⚠⚠ Un défaut écrit puis rattrapé le même jour — la purge n'aurait JAMAIS rien effacé
La garde de (1) épargnait
level = 0. Or les trois portes créent leurs comptes au niveau viewer (10) : aucune ligne n'aurait jamais satisfait la clause.⚠ Et rien ne l'aurait dit. Aucun compte non vérifié en base, donc « rien à purger » et « clause impossible » rendent le même silence — les deux comptages avant/après comptaient zéro tous les deux. ⚠ Son propre banc était d'accord avec elle : il vérifiait la chaîne
level = 0, c'est-à-dire sa propre recopie.Ce qui l'attrape : confronter la clause à ce que le code d'à côté écrit vraiment. Le banc lit désormais
LEVEL_VIEWERet extrait le niveau de chaqueINSERT.
✅ (3) LA PAGE SUIT L'ORDRE RÉEL — 27/08/2026
Trois étapes nommées, dans l'ordre vécu, et l'étape 1 donne enfin le moyen de la faire : le chat du site (qui envoie
REGISTERpuisIDENTIFYsur sa propre connexion), ou la commande pour qui a un client IRC. Le chapeau annonce les trois et prévient, avant toute saisie, que deux mots de passe distincts seront demandés.⚠ Un défaut introduit puis attrapé avant de sortir : en dérivant la durée du code de sa constante,
REG_CODE_TTLs'est retrouvée employée dans une branche et requise dans l'autre. La page aurait levé en fatale — mais seulement après une inscription réussie. C'est pourquoi le banc rend la page : une constante absente est une fatale, pas un texte manquant.✅ (4) LE CYCLE EST ÉPROUVÉ — 27/08/2026
Web/tests/test-cycle-inscription.php, 34 constats, 6 blessures sans trou.Il couvre les deux jointures que rien ne mesurait : la casse (le Tcl passe le code en majuscules avant de comparer — une minuscule dans l'alphabet et aucun code ne correspondrait jamais) et les séparateurs (le Tcl retire
-,_et l'espace — si l'alphabet en contenait un, le code serait mutilé). Il compare aussi pour de vrai leSHA2de MySQL auhash()de PHP, au lieu de croire qu'ils se valent.Et il rejoue le cycle en base : compte inerte, jeton en empreinte seule, recherche par le chemin du bot sur un code écrit avec un tiret, accès qui s'ouvre, usage unique, ménage vérifié. ⚠ Le monde réel ne contient pas le sujet — la base ne porte que deux comptes, tous deux vérifiés : le banc fabrique les siens.
⚠⚠ Un constat qui n'est PAS une exigence :
VERIFYne lit ni grade ni auth. Le banc le constate au lieu de le corriger, pour que le jour où quelqu'un croit le contraire, une ligne le détrompe. Seulnickguardprotège le pseudo, avec 60 s de grâce.
| Pour fermer | ✅ RIEN — CONSTATÉ FERMÉ le 27/08/2026. Les quatre chantiers sont faits — (1) l'expiration 24 h, (2) la doctrine des secrets, (3) la page dans l'ordre réel, (4) le banc du cycle — et le dernier geste est tombé : IriX a redémarré à 11:59:05 et son balayage horaire a purgé pour de vrai à 12:00:00 (trace dans IriX/cmds/IriX-sql.log). ⚠ Ce qui n'a PAS été constaté par SerVuS : le rendu de ?p=register dans un vrai navigateur — il est éprouvé par test-inscription-parcours.php, pas par un œil. ⚠ Il reste UNE décision, et elle n'appartient pas à ce point : Web/inc/ircauth.php est la dernière porte par laquelle le mot de passe du RÉSEAU atteint PHP. Le retirer supprimerait deux fonctionnalités — l'identification depuis le site et l'accès aux salons réservés (salonauth.php). C'est une décision de SerVuS, pas une correction ; en attendant, la page ne le tait plus et le banc le garde comme exception nommée. |
|---|
51. ✅ Une campagne interrompue bloquait le banc du serveur POUR TOUJOURS — CONSTATÉ FERMÉ le 25/08/2026
Vécu le 25/08/2026, et c'est ainsi qu'il a été trouvé : ./Tools/bancs.sh annonçait « 1 échec sur 105 » sans dire lequel ni pourquoi. Le banc en cause, run-tests.sh, n'avait aucun défaut — il était empêché de démarrer.
La chaîne, mesurée de bout en bout :
- Une campagne qui monte de vrais serveurs (
test-link.pyet ses voisines) est interrompue — délai dépassé,Ctrl+C, processus tué. SonfinallyPython ne s'exécute pas. - Ses
overtchatdsurvivent, détachés, avec une configuration dans/tmp. run-tests.shs'ouvre sur une garde :pgrep -f build/overtchatd→ « Un overtchatd tourne déjà. Arrêtez-le. » La garde est juste et son message dit exactement quoi faire.- Mais elle voit désormais les orphelins de l'étape 2. Le banc refuse de démarrer, et refusera à chaque passage, jusqu'à ce qu'un humain fasse le
pkillà la main.
⚠⚠ RIEN NE SE RÉARME. Ce n'est pas un échec qui passe : c'est un auto-blocage définitif. Le jour où il s'installe, le projet perd ses 17 campagnes — celles qui éprouvent le vrai binaire sur de vraies sockets — et le lanceur continue d'annoncer un chiffre rassurant juste au-dessus.
⚠⚠⚠ ET LE LANCEUR JETTE LA SEULE CHOSE QUI AURAIT SERVI. Il exécute la campagne ainsi :
if bash "$CAMPAGNE_SERVEUR" >/dev/null 2>&1; then
Le message de la garde — deux lignes, dont la commande exacte du remède — part dans /dev/null. Le lanceur n'affiche que « ÉCHEC (relancer seul pour le détail) ». L'information existait, à une redirection près. C'est la parenté du point 34 : un banc qui dit vrai, et que personne n'entend.
Trois défauts distincts, mesurés le 25/08/2026 :
| # | Ce qui manque | Mesuré |
|---|---|---|
| a | run-tests.sh n'a aucun trap — il ne porte que set -u | grep -c trap → 0 |
| b | Le lanceur avale la sortie de la campagne | la redirection ci-dessus |
| c | Aucune campagne n'efface son répertoire temporaire | 14 appellent mkdtemp, 0 appellent rmtree — il en restait 47 dans /tmp ce jour-là |
⚠ Le (c) est bénin en place — 1,4 Mo — mais il grandit sans fin, et c'est lui qui rend le (a) diagnosticable : le nom du répertoire cité par un orphelin dit de quelle campagne il vient.
⚠ Ce que ce point NE dit pas : que la campagne est fragile. Relancée après nettoyage, elle passe 17/17, et quand elle va au bout elle termine bien ses serveurs. Le défaut n'est que dans le chemin d'interruption.
| Pour fermer | ✅ RIEN — CONSTATÉ FERMÉ le 25/08/2026, et par une MUTATION, pas par une relecture. Les trois défauts sont traités : le lanceur affiche désormais ce que la campagne a dit au lieu de le jeter ; run-tests.sh porte un trap et un réarmement au démarrage ; les répertoires temporaires sont purgés sur le critère « plus aucun processus vivant ne s'en sert ». Éprouvé : un orphelin fabriqué exprès (parent mort, conf de campagne) est retiré et le banc démarre seul — puis, contre-épreuve, avec une vraie campagne en vol il refuse toujours, donc la garde n'est pas devenue permissive. Production intacte du début à la fin (PID inchangé, ses trois ports). Les répertoires sont retombés de 75 à 1. ⚠⚠ DEUX CHOSES QUE LA MUTATION A TROUVÉES ET QUE LA LECTURE N'AURAIT PAS DONNÉES, toutes deux corrigées : (1) bash n'exécute son trap qu'à la fin de la commande en cours — tant que le script attend une campagne, un SIGTERM reste en attente, et un SIGKILL n'est interceptable par personne : le trap seul laissait revenir le défaut par la porte qu'il ne ferme pas ; (2) le critère « son parent est init » était faux sous WSL, où l'orphelin est adopté par le /init de la session, dont le PID varie — il marchait sur le cas d'origine et aurait échoué sur le suivant, sans que rien ne le signale. Le critère ne dépend plus de la parenté. ⚠ Ce qui reste vrai pour la prochaine fois : on ne tue jamais sur le motif overtchatd, la production tourne sur cette machine ; c'est le segment build/ qui rend le motif incapable de l'atteindre. |
|---|
50. ✅ Le bilan public comptait des rubriques qui n'étaient pas celles annoncées — CONSTATÉ FERMÉ le 25/08/2026
Trouvé le 25/08/2026, en relisant VERSION.md. Ce n'est pas un chiffre périmé : c'est une règle fausse, lue par les contributeurs comme par les visiteurs.
Le préambule de VERSION.md présente un tableau de sept rubriques, puis affirme que les nombres du bilan « additionnent les entrées rangées sous les rubriques du tableau ci-dessus », et conclut : « écrire ses changements sous les rubriques est donc ce qui les fait compter ».
⚠⚠ journal_rubriques(), dans Web/inc/journal.php, n'en connaît pas sept. Et l'écart joue dans les deux sens :
| Ce que le tableau annonce | Ce que le code compte |
|---|---|
Créations, Modifications, Suppressions, Évolutions, Bugs corrigés | ✅ comptées |
Erreurs | ❌ jamais comptée |
Avertissements | ❌ jamais comptée |
| (absente du tableau) | Vérifié, libellé « constats » — comptée, mais le tableau ne la montre pas |
Mesuré le 25/08/2026 : VERSION.md porte 55 sections « Erreurs » et 120 « Avertissements ». Ce sont donc 175 sections que le bilan de ?p=versions ignore, alors que le paragraphe juste au-dessus promet au lecteur qu'elles comptent.
⚠⚠⚠ ET UN RENOMMAGE A ACHEVÉ LE MÉCANISME, SANS QUE RIEN NE ROUGISSE. La reconnaissance cherche le mot Vérifié. Or les versions récentes n'écrivent plus « Vérifié » mais « Éprouvé » — mesuré : 9 sections « Éprouvé », toutes récentes, toutes comptées pour zéro. Un mot changé dans un titre a suffi ; aucun banc ne l'a vu, parce qu'aucun banc ne croise les deux listes.
⚠ Le rapport avec le point 49 s'arrête à la page. Le 49 est un défaut de rendu — ce qu'on voit ; celui-ci est un défaut de comptage — ce qu'on additionne. Les deux vivent dans Web/inc/, ils ne se corrigent pas ensemble.
| Pour fermer | ✅ RIEN — CONSTATÉ FERMÉ le 25/08/2026, sur la page servie. Les huit rubriques comptent, et un banc exige que les deux listes coïncident : test-journal.php compare le tableau de VERSION.md et journal_rubriques() comme deux ensembles, dans les deux sens. Recopier la liste n'aurait pas suffi — deux listes tenues à la main divergent, c'est ce qui venait de se produire. Mesuré sur ?p=versions : 120 erreurs et 246 avertissements, qui ne comptaient pour personne, apparaissent au bilan. ⚠⚠ LA DÉCISION QUI APPARTENAIT À SERVUS a été prise en son absence, à sa demande, et elle se défait en retirant une ligne : « Erreurs » et « Avertissements » comptent. Parce que le préambule le promettait déjà à qui écrivait sous elles ; parce que ne pas les compter obligeait à retirer cette promesse ; et parce que ce dépôt dit ce qui a raté — un bilan qui masque ses erreurs vaut moins que celui qui les affiche. Les nombres publics augmentent, et c'est voulu : ils deviennent justes. ⚠ Le renommage est ce qui avait fait le mal : « Éprouvé » avait remplacé « Vérifié » dans les titres, neuf sections ne comptaient plus, et rien ne rougissait. Les rubriques portent maintenant leurs synonymes — une rubrique peut changer de nom, son compte ne disparaît plus avec. Deux mutations mordent : oublier une rubrique, retirer un synonyme. |
|---|
49. ✅ Le convertisseur de la page publique était plus strict que le markdown — CONSTATÉ FERMÉ le 25/08/2026
Trouvé le 25/08/2026 en cherchant autre chose, et il ne se voyait que sur la page servie — jamais dans le fichier source, jamais dans un banc.
md_render(), dans Web/inc/markdown.php, ne reconnaît un titre qu'à cette condition :
if (preg_match('/^(#{1,4})\s+(.*)$/', $line, $m)) {
Le motif est ancré en colonne 0. Un titre indenté ne serait-ce que d'un espace cesse d'être un titre : il tombe en paragraphe, et le visiteur lit ses dièses en clair. C'est arrivé une fois, dans ce carnet même — un #### indenté de deux espaces à l'intérieur d'un élément de liste, corrigé le 25/08 en le passant en gras.
⚠⚠ ET LE CONVERTISSEUR SE CONTREDIT LUI-MÊME. Les citations sont traitées juste au-dessus des titres, et leur motif, lui, tolère l'indentation :
if (preg_match('/^\s*>\s?(.*)$/', $line, $m)) {
Mesuré le 25/08 : Markdown/ROADMAP.md porte 32 citations indentées, et elles se rendent toutes. Une citation indentée passe, un titre indenté non — dans le même fichier, à quelques lignes d'écart. Ce n'est donc pas un choix, c'est un oubli.
⚠ CommonMark tolère 0 à 3 espaces avant un titre ATX. Le convertisseur est plus strict que la norme, et rien ne le dit — ni dans le fichier, ni dans test-markdown.php.
⚠⚠⚠ ET LE TITRE INDENTÉ N'ÉTAIT QUE LA PETITE MOITIÉ — TROUVÉ LE 25/08/2026 EN BALAYANT LE CORPUS ENTIER. Le même convertisseur n'a aucune branche <ol> : il ne connaît pas les listes numérotées. Une liste 1. 2. 3. n'est pas rendue comme une liste — ses items sont FONDUS dans un seul paragraphe, à la suite les uns des autres. Le visiteur lit :
1. poser le mot de passe dans chaque overop.conf — inerte tant qu'on ne reconnecte
pas ; 2. poser les empreintes dans pseudos-proteges.conf ; 3. REHASH du serveur…
Mesuré sur les 27 fichiers .md du dépôt : 231 items, dans 70 blocs, sur 18 fichiers. Et ce ne sont pas des cas latents — 31 de ces blocs sont dans les deux fichiers PUBLICS : 22 dans ce carnet, 9 dans VERSION.md.
⚠ C'est le plus gros défaut de rendu du projet, et il était invisible parce qu'il ne produit aucun caractère parasite : le texte est là, entier, seulement mis à plat. Rien ne rougit, rien ne manque à la lecture du fichier source — il faut regarder la page servie.
⚠⚠ CE DÉFAUT NE SE CORRIGE PAS DANS LES DOCUMENTS. Transformer 231 items numérotés en puces détruirait précisément ce qu'ils portent : un ordre. « D'abord ceci, ensuite cela » n'est pas une énumération. La correction est dans le convertisseur, et nulle part ailleurs.
Deux autres écarts, plus petits, et aucun des deux n'a de cas aujourd'hui :
| Écart | Mesuré le 25/08/2026 |
|---|---|
| titre indenté de 1 à 3 espaces non reconnu | plus aucun cas dans le corpus depuis la correction — la garde vaudrait pour la récidive, pas pour un défaut en cours |
#{1,4} : les niveaux 5 et 6 ne sont pas gérés du tout | aucun cas dans le corpus — latent |
⚠⚠ POURQUOI CE N'EST PAS CORRIGÉ DANS LA FOULÉE. Élargir le motif change le rendu de tous les documents servis, VERSION.md compris, pour un défaut qui n'a plus aucun cas au moment où ces lignes sont écrites. Cela se tranche ; cela ne se glisse pas dans une passe de carnet. ⚠ Et l'élargissement a un effet de bord à mesurer avant, pas après : une ligne indentée qui commence par un # sans être un titre — un commentaire dans un extrait montré hors bloc clôturé — en deviendrait un.
⚠ Ce que ce point NE demande pas : de refaire le convertisseur. Il porte une question à trancher, et une seule.
| Pour fermer | ✅ RIEN — CONSTATÉ FERMÉ le 25/08/2026, sur la page SERVIE. Les trois écarts sont traités. (1) Les listes numérotées ont leur branche <ol> : la page publique en rend 30, pour 90 items, et il ne reste aucun paragraphe où une liste soit fondue. (2) Les titres tolèrent 0 à 3 espaces, (3) et vont jusqu'au niveau 6. ⚠ L'effet de bord est mesuré : aucune ligne des 27 fichiers .md ne devient un titre par cet élargissement — le rendu des deux pages publiques est donc inchangé, la garde vaut pour la suite. 123 constats, 0 échec, et deux mutations qui mordent. ⚠⚠ CE QUE LA MUTATION A TROUVÉ, ET QUI VALAIT LE DÉTOUR : mes deux premiers constats de faux positifs étaient CREUX. Ils plaçaient le faux numéro après un paragraphe, où c'est la règle de rupture qui protège — pas la garde. En élargissant la garde à tout \d+., ils restaient verts tout en cassant précisément ce qu'ils prétendaient garder. Il a fallu les refaire avec le nombre en tête de bloc, seul endroit où la branche liste le voit en premier. ⚠ Les deux faux positifs sont de vrais cas du dépôt : un port (8123.) en tête de ligne, et 42. à la suite d'une phrase. On n'ouvre une liste que sur 1. — les 33 vraies listes du corpus commencent toutes par 1, c'est mesuré. |
|---|
48. 🔨 Refaire le panel de l'installateur au format du site, avec un mode auto et un mode manuel — DEMANDÉ par SerVuS le 24/08/2026
Ce n'est pas un défaut : c'est un chantier à ouvrir plus tard, et SerVuS le dit lui-même en le demandant — « je sais que c'est un gros chantier ». Noté ici pour qu'il ne se perde pas, pas pour être entamé.
Ce qui est demandé, en trois morceaux qui peuvent avancer séparément :
- Un panel qui ressemble au site.
overtchat-setup.shrend aujourd'hui des encadrés Unicode en ANSI — c'est déjà soigné, mais cela ne ressemble pas au site.
🔧 Le socle a été ASSAINI le 28/08/2026 — le chantier reste ouvert
Avant de refaire l'apparence, on a mesuré celle qui existe. La couche de rendu était complète et n'avait pas un seul banc : rien ne touchait
_ui_vlen,_ui_pad, la bornure de largeur, ni le repli sans terminal. Le seul banc voisin,test-palette.php, croise les couleurs avecapp.css— pas la géométrie.⚠⚠ Le premier banc écrit a trouvé un cadre qui ne se refermait pas. Une ligne plus longue que le cadre poussait la bordure droite dehors :
_ui_padborne son rembourrage à zéro, donc rien ne repliait le texte. Ce n'est pas un cas d'école — la largeur a pour plancher 64 colonnes, et le sous-titre du bandeau porteversion · user@host · chemin d'installation.⚠⚠ Et un balayage des quinze menus en a trouvé un seul de fautif, mais il en cumulait quatre : le sous-menu « Services optionnels » offrait
[0] Retoursans branche, appelait_menu_addsans_menu_has(des clés construites puis jamais consultées), n'avait aucune branche par défaut — un choix invalide ne produisait rien du tout — et s'affichait hors cadre. Plus_irix_choose_mode, seul menu du fichier sans sortie annoncée, alors qu'il décide quels modules le bot chargera.⚠ Ce qui reste au morceau (1), et la voie est toujours celle tranchée au 25/08 — TUI par défaut, option
--webquand PHP est là :(a) trois menus dessinent encore leurs entrées en texte brut hors cadre✅ FAIT le 30/08/2026 — voir juste en dessous ; (b) l'option--webelle-même, qui ne peut pas être ouverte sans décision — voir ci-dessous.✅ (a) — les trois menus, repris le 30/08/2026.
_eggdrop_grades_menu,_panel_service_detailet_panel_server_detaildessinent désormais dans le cadre (_ui_title/_ui_top/_ui_item/_ui_bot/_ui_prompt), comme les douze autres. ⚠⚠ La cause n'était PAS celle qu'on cherchait : pas un débordement de largeur — la plus longue entrée tient en 59 colonnes pour un cadre qui en fait au moins 64, et ces heredocs ne portaient ni couleur ni caractère large. Ils n'ouvraient simplement aucun cadre : le bandeau se fermait, et les entrées flottaient dessous. « Hors cadre » était littéral. ⚠ Deux aggravants que le registre ne mentionnait pas : les deux panels de détail appelaient_services_overview/_servers_overview— dont le contrat ÉCRIT exige « un cadre déjà ouvert par l'appelant » — sans en ouvrir un. Ils produisaient donc des bordures│ … │et un├───┤sans toit ni plancher, puis des_kvhérités sans aucune bordure : trois dialectes empilés dans un même écran. ⚠⚠ Et pourquoitest-ui-cadre.shne pouvait pas les voir : il filtre sur_menu_add/_menu_gate, que ces trois-là n'emploient pas. Il prouve qu'un cadre ouvert se referme à la même colonne — chez eux l'invariant était vrai et VIDE. Le banc était exact, utile, et muet sur ce défaut. 📌 Ce qu'un banc ne regarde pas ne se voit qu'en changeant la QUESTION, jamais en durcissant la mesure existante. Trois constats neufs, dérivés et non énumérés : tout écran de menu ouvre un cadre · le plancher qui empêche le vert silencieux · plus aucun heredoc de menu. ⚠ Le banc était aussi plus fragile que le code qu'il éprouve : il n'extrayait pas_ui_localeet rougissait (24/36) dans un shell sansLANGUTF-8,${#s}comptant alors des octets. Il pose désormais sa locale. Un banc qui rougit selon l'environnement apprend à ne plus être cru.🔴 TROIS DÉCISIONS AVANT TOUT
--web, et aucune n'est technique.
- ⚠⚠ Un
php -Slancé par l'installateur tourne en ROOT, et sous Linux tout utilisateur local peut atteindre127.0.0.1:<port>: il n'y a pas d'isolation de port par utilisateur. C'est une exécution de code arbitraire déguisée en installateur. Un jeton d'URL à usage unique, imprimé sur le terminal, est la seule parade qui n'invente pas un compte que l'exploitant n'a pas encore — car_access_login_gatene s'applique qu'à partir de la deuxième exécution, et le premier lancement est justement celui où--webservirait.- ⚠ Le CSS n'est pas là au moment où il servirait. L'installateur arrive seul, en un fichier ;
site.cssn'atteint la machine qu'après l'étape 12. Trois issues, toutes coûteuses : l'embarquer dans le script, le télécharger à part, ou n'offrir--webqu'après l'étape 12.- ⚠ Et ce serait le MAUVAIS fichier. Le TUI porte la palette d'
app.css, pas desite.css— c'est ce quetest-palette.phpcroise déjà, contre ce qu'un commentaire du script raconte. Un--webbâti sursite.cssne ressemblerait pas au panel qu'il remplace.⚠ Dernier point mesuré, et il pèse : 95 lectures
readdirectes ne passent par aucune abstraction.--autoa résolu le problème en contournant tous les menus ; un mode web ne peut pas contourner, il doit répondre. Sans une couche_askqui n'existe pas,--webne serait pas un panel mais un formulaire par-dessus le mode auto — ce qui est peut-être la bonne réponse, mais c'est une décision de conception, pas un détail.
2. ✅ Un mode AUTO et un mode MANUEL — ÉCRIT le 27/08/2026. Auto : site + serveur + services, sans question. Manuel : l'exploitant choisit ce qu'il pose — c'est l'assistant, il existait déjà. Détail au bas de ce point. 3. Selon l'édition dont on dispose — gratuit, payant, développement.
⚠⚠ LE TROISIÈME MORCEAU EST LE POINT 9, PAS UN CHANTIER À PART. « Gratuit / payant / développement » sont les éditions de Markdown/EDITIONS.md, et le point 9 porte déjà leur découpe. Un installateur qui propose des modes « en fonction de la version » suppose que les éditions existent et qu'un binaire sait dire laquelle il est. Cela ne peut donc pas précéder le point 9 — au mieux l'accompagner.
⚠ Ce renvoi a été corrigé le 25/08/2026 : il disait que le point 9 est « à ne pas ouvrir tant que le registre a des points ouverts ». Cette phrase appartient au texte d'origine gardé du point 9, et elle est périmée — le point 9 est ouvert depuis le 14/08/2026 sur décision de SerVuS, ses phases 1 et 2 sont faites. Ce qui tient encore, en revanche, c'est l'ordre entre les phases : la phase 3 est justement l'installateur par mode, et le point 9 explique pourquoi elle ne s'ouvre pas la première.
Les voies possibles pour « au format du site », et ce que chacune coûte
| Voie | Ce que ça donne | Ce que ça coûte |
|---|---|---|
| TUI bash enrichi (l'existant, poussé plus loin) | couleurs 256 ou truecolor, alignement, barres de progression | reste du texte : on s'approche, on n'y ressemble pas · aucune dépendance |
dialog / whiptail | vraies boîtes ncurses, navigation au clavier | une dépendance de plus avant d'installer quoi que ce soit · et le rendu est daté, plus loin du site que l'existant |
| ⭐ interface web locale | le design du site, littéralement — on réutilise site.css | ⚠⚠ poule et œuf : il faudrait PHP avant que l'installateur ait posé PHP · et une installation se fait souvent en SSH, sans navigateur |
gum / charmbracelet | très joli, moderne | dépendance Go à télécharger — inacceptable pour un installateur qui doit tourner sur une machine nue |
La piste qui tient debout est un compromis : garder le TUI comme chemin par défaut — il marche partout, y compris en SSH sur une machine nue — et offrir une option --web qui, quand PHP est déjà là, lance un php -S 127.0.0.1:<port> servant une interface reprenant site.css. L'installateur sait déjà faire les deux gestes : il pose PHP, et il connaît _site_install.
⚠ Le mode AUTO, lui, ne dépend d'aucune de ces voies : c'est un enchaînement des étapes existantes sans les questions. Il pourrait se faire avant le reste, et il servirait tout de suite — l'installation de bout en bout demande aujourd'hui une quinzaine de réponses, ce qui est aussi ce qui rend son épreuve laborieuse.
✅ (2) LE MODE AUTO EST ÉCRIT — 27/08/2026
--auto enchaîne les six étapes de l'assistant sans poser une seule question : site → serveur IRC → Eggdrop → IriX → unités systemd. Le mode manuel, lui, n'a pas bougé — c'est l'assistant, et il existait déjà.
⚠⚠ LE DANGER N'ÉTAIT PAS QU'IL ÉCHOUE : C'EST QU'IL RÉUSSISSE EN APPARENCE. Un read sans personne devant ne rend pas une réponse — il rend une chaîne vide obtenue par EOF, que plus rien ensuite ne distingue d'une réponse donnée. L'installation se serait poursuivie sous des valeurs que personne n'a choisies, en affichant « configuré ». Le mode auto ne lit donc jamais l'entrée standard : _site_ask rend la valeur prévue et le DIT, sur stderr — jamais sur stdout, qui est la réponse puisque la fonction est appelée dans un $( ).
Les deux pièges trouvés en l'écrivant, et qu'aucune relecture n'aurait donnés :
| Piège | Ce qu'il aurait produit |
|---|---|
Le gel. Les trois boucles de _site_install reposent leur question tant que la réponse ne convient pas | En auto, la réponse suivante est identique à la précédente : le refus se répète sans fin et sans rien écrire à l'écran. Pas un échec — un arrêt muet. _auto_boucle coupe au second tour, parce qu'un second tour n'est pas une seconde chance : c'est la preuve que la valeur fournie est refusée |
| Le secret vide. Le mot de passe SQL d'IriX n'a de défaut nulle part | IriX.json écrit avec pass:"", l'écran disant « configuré », et le bot ne joignant jamais la base. _auto_prevol l'exige donc avant la première écriture sur le disque |
Ce qui arrête le départ — contrôlé pendant que rien n'est encore écrit : le nom de l'installation (il ne se devine pas et ne se change pas après coup), OVERTCHAT_SQL_PASS, et une élévation sans mot de passe (_as_root passe en sudo -n sous --auto : une invite attendrait sans fin et sans message). Le reste se donne par l'environnement — OVERTCHAT_DOMAINE, _RESEAU, _COURT, _MAIL_FROM, _MAIL_TO, _SERVEUR — et ce qui manque est annoncé au lieu d'être subi.
⚠ Une question de consentement reçoit « non », et une seule : installer une archive dont l'empreinte n'a pas pu être vérifiée. Les autres portent sur ce que l'installation vient faire — --auto est cet accord. Celle-là demande de passer outre un contrôle d'intégrité sur une archive qui s'extrait par-dessus la racine : un accord donné d'avance, par un exploitant absent, n'en est pas un.
⚠ Le défaut de serveur est unrealircd, pas celui du projet. overtchat_ircd n'est distribué par aucun canal — c'est la décision encore ouverte au registre —, donc un mode auto qui le choisirait échouerait chez tout tiers, au milieu de l'installation.
Éprouvé : Bootstrap/tests/test-auto.sh, 42 constats, et une passe de mutation à 16 blessures, sans un seul trou — dont le gel, le secret vide, l'alerte écrite sur la mauvaise sortie, et l'ordre du contrôle avant vol. ⚠ Le constat qui vaut pour la suite est celui de complétude : il compare le nombre de boucles de question de _site_install au nombre de gardes, donc une boucle ajoutée demain sans sa garde fera rougir le banc. ⚠ Le mode a aussi été lancé pour de vrai, dans un HOME détourné : il refuse en nommant les trois manques, sort en code 2, ne crée rien, et le journal de la production est resté intact — mesuré, pas supposé.
⚠⚠ CE QUI N'EST PAS CONSTATÉ, ET QUI RESTE À FAIRE : une installation complète de bout en bout n'a jamais été menée. Le banc éprouve les gardes, pas le parcours — il faudrait une machine nue, une trentaine de minutes, et la compilation d'un serveur IRC. C'est le geste qui manque, et il va sur la machine tierce (Ubuntu-24.04), pas ici : networkingMode=mirrored fait que tous les ports de la production sont déjà pris. ⚠ Le nommer ici plutôt que de cocher le morceau : écrire du code prouve qu'il existe, jamais qu'on peut s'en servir.
| Pour ouvrir | Le troisième morceau demande la phase 3 du point 9 — sans installateur par édition, « en fonction de la version » n'a pas d'objet ; et cette phase est celle qui brancherait un lecteur mort, donc elle ne s'ouvre pas la première. ⚠ --web quand PHP est déjà là) et le troisième. |
|---|
29. ✅ Une seule sauvegarde par jour — LIVRÉ et CONSTATÉ le 16/08/2026
Demandé par SerVuS : « en sauvegarde sur mon disque externe (Freebox), génère qu'une sauvegarde par jour ». Mesuré : trois archives le 12/08, deux le 13/08, deux le 15/08.
⚠⚠ ET LE PREMIER DIAGNOSTIC ÉTAIT FAUX — la leçon vaut plus que le correctif. J'ai accusé le rattrapage au démarrage (Persistent=true). Le journal dit le contraire : croisé avec la liste des démarrages, le service a tourné exactement une fois par jour, jamais deux — le 14/08 comme le 15/08, malgré trois démarrages chacun. Les archives surnuméraires venaient d'appels hors systemd, à la main ou par Tools/desinstaller.sh, dont la date de modification coïncide avec l'une d'elles à la seconde près.
⚠ Et le rattrapage n'était pas en cause : aucune sauvegarde n'a jamais eu lieu à 04:00 sur cette machine — les six passages automatiques sont tous des rattrapages. Le retirer n'aurait pas réduit le nombre d'archives : il l'aurait mis à zéro, en silence.
⚠⚠ ET LE PREMIER CORRECTIF AURAIT CASSÉ UNE GARDE. Il appliquait la règle à tous les appels. Tools/desinstaller.sh sauvegarde avant toute suppression et s'arrête si elle échoue : il aurait vu un succès, cru avoir son filet, et supprimé l'installation en s'appuyant sur une archive vieille de plusieurs heures — d'avant justement ce qu'on efface. C'est l'objet que ce script refuse de produire partout ailleurs : une confirmation mensongère.
Le sens retenu : le défaut est « on sauvegarde ». Seule l'unité systemd demande --quotidien. Un appel explicite produit toujours une archive.
Constaté en service : systemctl cat montre l'option chargée, NeedDaemonReload=no, et l'épreuve à blanc — commande exacte de l'unité, une archive du jour déjà présente — s'arrête sans rien produire.
⚠ Le banc test-backup.php rougissait sur du code juste : son motif ancrait --with-db en fin de ligne, donc toute option supplémentaire le faisait tomber. Un banc doit vérifier qu'une option est là, pas qu'elle est la dernière. Corrigé, plus un constat pour --quotidien.
⚠ Reste ouvert : la limite distante est de 30 archives, et la rotation n'a donc jamais rien supprimé sur la Freebox. À une par jour, elle commencera à agir dans trois semaines. Rien à faire pour l'instant, mais c'est la première fois qu'elle tournera pour de vrai — à constater ce jour-là.
25. ✅ Géolocalisation facultative de l'ASV — CONSTATÉ FERMÉ le 25/08/2026, le troisième tiers étant IMPROVOCABLE
✅✅ ÉCRITE ET LIVRÉE EN 4.19.45, et le récit compte plus que le résultat : quatre défauts ont été trouvés en ÉPROUVANT, aucun en relisant.
La première écriture rendait « le lieu le plus proche » — la réponse évidente, et la fausse. Mesurée sur des positions réelles :
| Position | Ce qui sortait | Pourquoi |
|---|---|---|
| Lyon | « Mutualité Préfecture Moncey » | un quartier est toujours plus proche que le centre-ville |
| Paris | « Paris 04 Hôtel-de-Ville » | un arrondissement, même cause |
| Montréal | « Ville-Marie » | idem |
| Bruxelles | « Brussels » | GeoNames nomme en langue locale ou en anglais |
| Genève | « Geneva » | idem |
⚠⚠ ET LES ARRONDISSEMENTS NE SE DISTINGUENT PAS PAR LEUR TYPE : GeoNames classe « Paris 04 Hôtel-de-Ville » en PPL, exactement comme « Paris ». Seul le nombre d'habitants les sépare — 27 332 contre 2 138 551. C'est ce qui a imposé la règle finale : dans un rayon proche, le plus PEUPLÉ l'emporte ; au-delà, le plus proche.
⚠ Trois corrections successives, chacune après une mesure : écarter les quartiers à l'import · préférer le plus peuplé · reprendre les noms français depuis le jeu de données par langue. ⚠ Les quartiers restent tapables — leur nom est accepté à la saisie, mais une position ne peut jamais les désigner. La liste des noms acceptables et celle des lieux désignables sont deux choses.
Les quatre points durs de l'énoncé d'origine sont traités :
- Les coordonnées sont ajoutées EN FIN (colonnes 4, 5 et 6 — latitude, longitude, population). Les insérer avant le rang aurait fait de
$champs[2]une latitude, et « Paris » serait redevenu « París » au WHOIS. - Rien n'est conservé : ni base, ni journal, ni session. ⚠ Et la route est en POST, parce qu'Apache journalise la ligne de requête entière — une position en
GETfinirait en clair à côté de l'adresse IP, sans purge. - La case ne se recouvre pas avec « Afficher mon ASV » : l'une TROUVE la ville, l'autre décide de la MONTRER. Deux gestes, deux cases, jamais cochées par défaut ni l'une ni l'autre.
- Le refus ne produit aucune erreur : trois messages distincts selon la cause (refusé, indisponible, trop long), la case se décoche, et le formulaire reste pleinement utilisable. ⚠ La question du chiffrement ne se pose pas : sans HTTPS,
webchat_url()rendnullet ce formulaire n'est pas rendu.
✅ MESURÉ SUR LA PRODUCTION, AU NAVIGATEUR (23/08) : la case existe et n'est pas cochée · Lyon rend « Lyon » · Bruxelles rend « Bruxelles » · Berlin ne rend aucune ville · des coordonnées illisibles rendent 400 · et la même requête en GET est refusée (405), ce qui est la garde qui tient les positions hors des journaux.
⚠ Un gain de bord, qui dépasse la géolocalisation : puisque les noms français deviennent les noms principaux, quelqu'un qui tape « Geneve » voit désormais « Genève » s'afficher, là où il aurait vu « Geneva ».
⚠⚠ SIX CONSTATS DU BANC ÉTAIENT CREUX, et seule la passe de mutation les a vus. Trois cherchaient un mot que la mutation laissait en place — vider GEO_EXCLUS n'efface pas « PPLX » du commentaire au-dessus. Deux reposaient sur des données de fantaisie qui rendaient le même résultat avec et sans la règle. 26 constats, 14 mutations, 13 attrapées — la dernière est documentée comme redondante dans le banc lui-même.
⚠ Ce titre disait « RIEN N'EST ÉCRIT ». C'était vrai le matin du 16/08 et faux le soir. Ce point porte deux moitiés qu'il faut lire séparément : (a) la liste des villes arrive chez un tiers — ✅ tranchée, écrite et livrée en 4.19.2/4.19.3, il ne manque qu'un constat en installation tierce, plus une décision sur l'écrasement à la réinstallation ; (b) la géolocalisation elle-même — 🔴 rien n'est écrit, et c'est ce que le titre visait.
Demandé par SerVuS le 16/08/2026, juste après la fermeture du point 22 : proposer au visiteur, par une case à cocher, jamais par défaut, de laisser le navigateur donner sa position, afin de rapprocher au mieux le lieu réel — ou à défaut de viser la commune la plus proche de la liste.
Le besoin est net : aujourd'hui, une ville mal orthographiée ou absente de Config/villes.txt est refusée sèchement (« Ville inconnue — vérifiez l'orthographe, ou choisissez la commune la plus proche »), et c'est au visiteur de deviner laquelle est la plus proche. La géolocalisation répondrait à sa place.
⚠⚠ AVANT TOUT : LA LISTE DES VILLES N'ARRIVE CHEZ PERSONNE. Mesuré le 16/08/2026, et cela conditionne tout le reste — enrichir un fichier que personne ne reçoit ne changerait rien pour un tiers.
| Mesure | Résultat au 16/08 matin | Après correctif du 16/08 soir |
|---|---|---|
Config/villes.txt dans l'archive servie | absent — Config/ n'y apportait que identity.json.example | présent — ajouté à DISTRIB_RACINE_SITE |
/dl/Config/villes.txt | 404 | 404, et c'est voulu — voir ci-dessous |
| L'installateur mentionne-t-il « villes » ? | 0 occurrence | 0 occurrence, et c'est voulu — il dépaquette l'archive sans filtre, donc le fichier se pose seul |
| Taille de l'archive servie | 1 284 403 o (1,22 Mio) | 1 874 526 o (1,79 Mio, +46 %) |
Conséquence sur toute installation tierce : asv_villes_etat() rend absente, donc la garde !== 'absente' de asv_valider() ne se déclenche jamais et le contrôle de ville est désactivé en silence. ⚠⚠ Et il se conjugue au défaut du repli : la route fait asv_ville_trouver(...) ?? trim($saisie['ville']), donc la saisie brute part au realname, publié au /WHOIS de tout le réseau. L'invariant « la ville publiée vient de la liste, jamais de la frappe » — écrit trois fois dans le code — est faux chez tout le monde sauf ici.
✅ TRANCHÉ le 16/08/2026 : la liste part dans l'archive du site. Elle est entrée dans DISTRIB_RACINE_SITE, et non écrite en dur dans la ligne tar — cette liste sert deux fois, à l'archive et au calcul de fraîcheur qui décide de la refabriquer. Un chemin ajouté au seul tar aurait été emporté mais jamais surveillé : un ré-import par Tools/villes-importer.sh n'aurait pas régénéré l'archive, et les installations suivantes auraient reçu l'ancienne liste indéfiniment, sans qu'aucune alerte ne se lève.
⚠ Le chiffre annoncé ici était FAUX : ce paragraphe disait que « l'archive triplerait ». Mesuré : 1,22 → 1,79 Mio, soit +46 %. Les 2,03 Mio de la liste se compriment à 27,7 %, et le tar prend 0,07 s de plus. L'autre voie — faire appeler villes-importer.sh par l'installateur — a été écartée : elle aurait fait dépendre chaque installation de GeoNames au moment où elle tourne, c'est-à-dire déplacé le défaut au lieu de le fermer.
⚠⚠ CE QUI A LAISSÉ CE DÉFAUT VIVRE, et c'est le vrai enseignement. Un banc existait pour l'attraper — le constat « ⚠ tout ce que le site lit hors de Web/ part dans l'archive » de Web/tests/test-distrib.php — et il était vert. Sa dérivation ne cherchait qu'une écriture du chemin, __DIR__ . '/../../…', quand Web/inc/asv.php emploie dirname(__DIR__, 2) . '/Config/villes.txt'. Le gardien de l'invariant était donc aveugle au seul fichier qui lui échappait. Les deux formes sont désormais dérivées, et une passe de mutation l'a éprouvé : en retirant Config/villes.txt de la liste, le constat rougit avec ["Config/villes.txt"], là où il restait vert auparavant.
⚠ /dl/Config/villes.txt rend toujours 404, et il ne faut PAS le corriger : personne ne demande cette URL. Les extensions surveillées par la mise à jour sont sh json conf tcl — un .txt n'y entre jamais, et l'y faire entrer recréerait le 404 permanent avec rapport rouge toutes les six heures. Le fichier arrive par l'archive, qui est dépaquetée sans filtre ni liste blanche par _site_install(), exactement là où asv_villes_fichier() le cherche.
🔴 RESTE À TRANCHER — la réinstallation écrase la liste du tiers. _site_install() extrait sur $BASE_DIR sans exclusion. Un exploitant qui aurait relancé Tools/villes-importer.sh avec d'autres pays verrait sa liste remplacée en silence au prochain passage par le menu 12 — alors que Config/identity.json est protégé par un --exclude et Web/inc/config.php par un test d'existence. C'est une décision d'exploitation, pas un correctif : protéger la liste, c'est aussi empêcher toute mise à jour de la liste.
⚠ Rien n'est écrit, et il faut trancher AVANT d'écrire — quatre points durs, dont deux qui ne sont pas techniques :
- ✅ LA SOURCE PORTE DÉJÀ LES COORDONNÉES — ce point n'est PAS bloqué. Ce paragraphe affirmait que « la commune la plus proche est incalculable en l'état ». C'est vrai du fichier produit, et faux de la source :
Tools/villes-importer.shtélécharge les dumps GeoNames, qui portent 19 colonnes dont$5= latitude et$6= longitude. Sonawkles lit à chaque ligne et ne les recopie pas — les deuxprintdefiltrer()ne recomposent que trois champs. Les coordonnées traversent donc déjà la machine à chaque import. Aucune source nouvelle, aucune licence nouvelle : GeoNames est en CC BY 4.0, l'attribution est déjà dans l'en-tête du fichier.
⚠ Mais l'ordre des colonnes n'est pas indifférent. asv_villes() lit $champs[0] et $champs[2]. Ajouter en fin (nom⇥pays⇥rang⇥lat⇥lon) ne casse rien. Insérer avant le rang casse tout en silence : $champs[2] deviendrait une latitude, la règle « le nom principal l'emporte sur la variante » cesserait de s'appliquer, et « Paris » redeviendrait « París » au /WHOIS. ⚠⚠ Et aucun banc ne le verrait : test-asv.php fabrique ses listes de fantaisie avec trois colonnes écrites en dur, donc il n'éprouverait plus jamais le format réel tout en restant vert.
⚠ Le fichier passerait de ~2,1 Mo à ~4,3 Mo, relu et réindexé à chaque requête (asv_villes() n'a qu'un cache de processus, aucun cache persistant) — et un blob de cette taille entrerait dans l'historique git à chaque réimport. 2. ⚠ Une position est une donnée personnelle, l'ASV actuel n'en stocke aucune. Le point 22 le dit et c'est vrai : l'ASV vit le temps de la connexion, dans le realname, et rien n'est écrit en base. Une géolocalisation ne doit pas changer cela : la coordonnée sert à choisir un nom de ville, puis disparaît. Si elle devait être conservée, c'est une décision d'exploitation, pas un détail d'implémentation — et la charte devrait le dire. 3. ⚠ La double consigne se recouvre. Il y a déjà une case « Afficher mon ASV aux autres ». Une seconde case (« utiliser ma position ») en fait deux, et elles ne veulent pas dire la même chose : l'une décide qui voit, l'autre décide comment on trouve. Les confondre publierait une position à quelqu'un qui croyait seulement s'épargner une saisie. 4. ⚠ L'API du navigateur exige HTTPS et un geste de l'utilisateur, et elle échoue souvent (refus, minuteur, poste fixe sans GPS). Le formulaire doit donc rester entièrement utilisable sans elle : la géolocalisation est un raccourci, jamais un passage obligé. Et son refus ne doit pas produire un message d'erreur — c'est un choix, pas une panne.
| ✅ DEUXIÈME TIERS CONSTATÉ — LE REFUS | Le 24/08/2026, au navigateur, et c'est un vrai refus : navigator.permissions.query({name:'geolocation'}) rendait denied avant le clic. Rien n'a été simulé. |
|---|
| Le critère exige | Mesuré |
|---|---|
| la case se décoche | cochée juste après le clic → décochée après le rappel d'erreur |
| un message le dit | « Vous avez refusé le partage de position. Tapez votre ville. » |
| le formulaire reste utilisable | oui — champ Ville vide et saisissable |
⚠ Le troisième tiers a été éprouvé en BRANCHE, pas sur un vrai poste. En forçant le rappel d'erreur au code 2 (POSITION_UNAVAILABLE), le site rend bien un message différent : « Votre position n'a pas pu être obtenue. Tapez votre ville. » Cela prouve que la branche existe et se distingue — pas qu'un poste sans service de localisation se comporte ainsi. La simulation est assumée et dite.
⚠⚠ RIEN NE GARDAIT CES TROIS MESSAGES, et c'était le vrai trou : les fusionner en un seul texte générique n'aurait cassé aucun banc, et le visiteur qui a REFUSÉ aurait lu qu'une erreur est survenue — alors qu'il a simplement dit non. test-asv-geo.php passe de 26 à 33 constats et les garde.
⚠⚠⚠ ET LE PREMIER DE CES CONSTATS ÉTAIT CREUX — SEULE LA MUTATION L'A MONTRÉ. Écrit comme preg_match('/code === 1/', $chat) sur tout le fichier, il attrapait ev.code === 1006 et ev.code === 1007, des codes de fermeture WebSocket sans aucun rapport. Il serait resté vert alors même que la branche du refus avait disparu — la mutation le prouve, elle ne mordait pas. Le constat cherche désormais dans le voisinage du message, avec une frontière de mot. C'est le motif « motif trop large », attrapé par la seule chose qui l'attrape.
| ✅ PREMIER TIERS CONSTATÉ | Le 24/08/2026, en croisant trois sources indépendantes. SerVuS confirme : « la ville s'est bien remplie avec Lyon ». |
|---|
| Source | Ce qu'elle dit |
|---|---|
| SerVuS, à l'écran | le champ Ville s'est rempli, avec Lyon |
| le journal Apache | POST /index.php?p=asv-ville → 200, le 23/08 à 19:49:18, UA Chrome/151 — un Chrome ordinaire, distinct de mes passes |
| la fonction elle-même | asv_ville_proche(45.76, 4.84) rend 'Lyon' |
⚠ La troisième source porte sa propre contre-épreuve : la même fonction rend 'Paris' pour (48.86, 2.35) et 'Bruxelles' pour (50.85, 4.35). Elle ne rend donc pas « Lyon » à tout le monde — sans quoi la concordance n'aurait rien prouvé.
⚠ Ce qui rend ce constat solide : aucune des trois ne suffisait seule. Le journal prouve que la permission a été accordée mais pas ce qui s'est affiché ; la fonction prouve ce qu'elle sait rendre mais pas qu'on l'a appelée ; et une phrase seule n'est pas une mesure. Ensemble, elles ferment le premier tiers.
⚠⚠ RESTENT LES DEUX AUTRES TIERS, et ils ne laisseront jamais de trace : un refus de permission ne produit aucune requête, et un poste sans service de localisation non plus. Ceux-là demandent l'écran, et seulement lui.
⚠⚠⚠ UN TIERS DE CE CRITÈRE EST DÉJÀ PRIS, ET LA PREUVE DORMAIT DANS UN JOURNAL QUE PERSONNE N'OUVRE — trouvé le 24/08/2026. Le journal d'accès Apache (/var/log/apache2/irix_ssl_access.log) porte, le 23/08 à 19:49:18 :
192.168.1.254 "POST /index.php?p=asv-ville HTTP/1.1" 200
referer: …/index.php?p=chat UA: Chrome/151.0.0.0
⚠ Ce navigateur n'est pas un des miens : mes passes portent Claude/… Electron/… Chrome/148, et on les distingue à l'œil dans le même journal. Celui-ci est un Chrome ordinaire.
Ce que cela prouve mécaniquement : chat.js n'émet ce POST que dans le rappel de succès de navigator.geolocation.getCurrentPosition, lui-même armé par le change de la case. Un navigateur n'appelle ce rappel qu'après qu'un humain a accordé la permission. La case a donc été cochée et la position autorisée. Quatre secondes plus tard, POST ?p=asv puis GET /ws 101 : la personne est entrée dans le chat.
⚠ Ce que le journal ne dit PAS, et il ne faut pas le sur-lire : la route rend 200 aussi bien pour une ville trouvée que pour ville: null, et le corps de la réponse n'est pas journalisé. Le geste est prouvé, son résultat à l'écran ne l'est pas. Une phrase de l'exploitant suffirait à trancher — « le champ Ville s'est-il rempli, et avec quoi ? »
⚠ Les deux autres tiers ne laisseront jamais de trace : un refus de permission ne produit aucune requête, et un poste sans service de localisation non plus. Ceux-là demandent l'écran.
| Pour fermer | ✅ RIEN — CONSTATÉ FERMÉ le 25/08/2026. (1) Autoriser et (2) refuser sont pris au navigateur par SerVuS, croisés sur trois sources indépendantes pour le premier, avec permissions.query à denied avant le clic pour le second. ⚠⚠ (3) LE TROISIÈME TIERS N'EST PAS PROVOCABLE, et c'est MESURÉ — il ne sera donc pas pris. Trois tentatives, toutes vaines : service de localisation de Windows coupé, fenêtre de navigation privée neuve, et enfin getCurrentPosition appelé à la main avec maximumAge: 0 pour interdire tout cache. Le navigateur a rendu une position à chaque fois, avec une précision de 323 m. ⚠ La raison, et elle vaut d'être connue : Chrome ne demande rien à Windows. Il envoie à Google la liste des réseaux Wi-Fi qu'il voit et en déduit la position — d'où ces 323 m, trop précis pour une adresse IP, trop grossier pour un GPS. Couper le service système n'y change rien, et l'override « Location unavailable » des outils de développement n'a pas mordu davantage. L'empêcher demanderait une machine sans Wi-Fi et sans GPS : un cas de laboratoire, pas un poste d'usager. ⚠ Ce qui garde le risque réel à sa place : le danger n'était pas la branche, c'était de fusionner les trois messages — quelqu'un qui a dit non lisant qu'une panne est survenue. test-asv-geo.php (33 constats) exige que les trois textes existent et diffèrent, et son premier constat sur ce point était creux avant d'être repris. Un point qui attend une preuve impossible ne protège rien. ⚠ Découvert au passage, et sans rapport avec ce point : maximumAge: 300000 autorise le navigateur à ressortir une position vieille de cinq minutes sans consulter personne. C'est un choix sain — il évite de rallumer la localisation à chaque clic — mais il rend tout essai trompeur si on l'ignore. |
|---|
26. ✅ L'intégrité — CONSTATÉ FERMÉ le 23/08/2026 sur une installation tierce, les deux moitiés
| ✅ CONSTATÉ FERMÉ | Le 23/08/2026, sur une Ubuntu 24.04 neuve installée pour l'occasion — installation tierce au sens strict : elle ne possède pas le dépôt et télécharge tout par HTTPS public. Les deux moitiés ont été prises, dans le même processus, donc sur le même manifeste figé. |
|---|
Moitié « ça aboutit » — 19:46. Menu IriX, « Synchroniser depuis le dépôt » : chaque module téléchargé et suivi de « empreinte vérifiée », puis « Modules allégés : 31864 → 17564 lignes » et « Sources IriX synchronisées ».
Moitié « c'est refusé » — 19:47. Sans quitter le menu, irix-alerte.tcl est altéré sur la production (une ligne ajoutée), le site sert bien la version altérée (32 266 octets, disque et réseau concordants), et la seconde synchronisation rend :
• Téléchargement : irix-alerte.tcl
✖ Empreinte incorrecte — fichier refusé : IriX/cmds/irix-alerte.tcl
✖ Synchronisation incomplète.
⚠ Et il ne refuse que lui : tous les autres modules de la même passe portent « empreinte vérifiée ». C'est la clause du critère — « un contrôle qui refuse tout est aussi inutile qu'un contrôle qui n'a jamais refusé ».
⚠⚠ LA PREUVE N'EST PAS LE MESSAGE, C'EST L'HORODATAGE. Un refus annoncé ne dit pas que le fichier a été épargné. Mesuré sur le disque de l'installation tierce :
| Fichier | Horodatage |
|---|---|
irix-alerte.tcl | 19:46:40 — celui de la synchronisation propre |
irix-botnet.tcl · irix-mandat.tcl · irix-edition.tcl | 19:47:30-31 — la seconde passe |
Le mv n'a pas eu lieu : la version saine est restée en place, et aucun résidu .part ou .tmp ne traîne. C'est ce que _download annonce — vérifier avant de poser — et c'est désormais constaté, pas lu.
⚠ La production a été remise en état dans la foulée : git checkout --, empreinte revenue à e01db0370e81a76d, git status vide. La fenêtre d'altération a duré moins de deux minutes.
⚠ Ce titre annonçait « l'intégrité promise n'est pas celle qui est faite » : c'est l'état du MATIN du 16/08. Les trois phases ont été écrites le jour même, et le corps du point le dit — « rien à faire côté site ». Ce qui reste n'est pas du code : c'est une installation tierce où un module altéré soit réellement refusé, et où une installation saine aboutisse. Tant que ce constat manque, le point reste ouvert — c'est la règle.
Le site publie tout ce qu'il faut. Presque personne ne le lit. Voilà le chantier en une phrase.
Ce qui est DÉJÀ fait, et qu'il ne faut pas refaire
Mesuré le 16/08/2026 en interrogeant le site, pas en lisant le code :
| Mesure | Résultat |
|---|---|
Manifeste servi (/dl/manifeste, /dl/manifeste.json, ?p=manifeste) | 200 aux trois |
| Empreintes publiées | 51, exactement les entrées de l'inventaire |
| Concordance manifeste ↔ fichier réellement servi | vérifiée fichier par fichier, tenue par un banc depuis la 4.18.7 |
En-tête X-Content-SHA256 sur chaque fichier | posé |
| Modules IriX déclarés par l'installateur | 25, les 25 couverts par le manifeste |
| Manifeste interrogeable par chemin (et non seulement par clé) | oui — chaque entrée porte son chemin |
Donc : rien à faire côté site. Le chantier est entièrement côté installateur.
Ce qui n'est PAS fait
Une seule entrée du manifeste est lue par un client : site, dans _site_install. Les cinquante autres sont calculées, servies, et consultées par personne. Partout ailleurs, le script enregistre l'empreinte de ce qu'il vient de recevoir au lieu de la comparer — _irix_sync_source, _overop_sync_source, _service_verify_sha256, _server_verify_sha256.
⚠⚠ C'est le motif exact que trois commentaires-stèles du script condamnent à propos de safe_encrypt, supprimée le 29/07/2026 parce que son « contrôle d'intégrité » enregistrait le hash du fichier qu'elle venait de télécharger. Le précédent n'a pas été évité : il a été déplacé.
Et _upd_apply_file porte un intertitre « Vérification d'intégrité » qui compare hash_dl à hash_expected — valeur écrite par _upd_check_file en hachant un téléchargement antérieur de la même URL sur le même serveur. ⚠ Pire : la garde est [[ -n "$hash_expected" && … ]], donc quand la valeur manque le contrôle est sauté en silence, sans même un avertissement.
Les trois phases, dans cet ordre
Phase 1 — l'honnêteté. ✅ FAITE le 16/08/2026, et constatée en production. Aucun changement de comportement : uniquement des phrases rendues vraies. Les pages servies ne portent plus aucune des affirmations ci-dessous, et la commande donnée au visiteur a été jouée telle quelle — 468 332 octets téléchargés, shebang correct, et l'empreinte annoncée concorde, donc la vérification manuelle promise est réellement faisable. L'ancienne forme, elle, échoue bien (curl 22).
| Où | Ce qui est écrit | Réalité |
|---|---|---|
Web/site/telechargement.php, encart « Ce que l'empreinte protège » | « chaque composant est vérifié avant d'être posé » | faux — seule l'archive site l'est |
Web/site/telechargement.php, carte « Installateur » | « vérifie leur empreinte avant de les poser, et refuse d'exécuter un fichier qui ne correspond pas » | faux |
Web/site/telechargement.php, en-tête du fichier | « deux versions… l'une est le binaire distribué » | faux — les deux clés servent le même .sh depuis l'abandon de shc |
Web/site/telechargement.php, bloc à copier-coller | curl -fsSLO …/?p=dl&f=setup puis sha256sum overtchat-setup | cassé : le & coupe la commande, -O n'a pas de nom, et le fichier servi s'appelle overtchat-setup.sh |
Web/site/guide.php, étape « 4 · IriX » | « dix-neuf fichiers… chacun avec son empreinte » | faux deux fois : ils sont 25, et aucune empreinte n'est vérifiée |
Markdown/SECURITY.md, « Chaîne de mise à jour » | « vérifie l'empreinte SHA-256 de chaque fichier après téléchargement » | faux hors du module _upd_* |
Markdown/SECURITY.md, « Intégrité des composants » | « option 7 du menu principal » | faux — c'est l'option 9 |
Web/inc/distrib.php | renvoyait à safe_encrypt verify au présent | ✅ corrigé en 4.18.7 |
Phase 2 — vérifier dans _download. C'est le cœur, et il a un point d'accroche unique : _download n'a que trois appelants — le dépouilleur (1 fichier), les modules IriX (git.irix.files, 28), le robot OverOp (2) — soit 31 téléchargements, tous au manifeste. ⚠⚠ Et c'est le seul endroit où le fichier est encore BRUT : voir le piège n° 1 ci-dessous.
⚠ Ce paragraphe a porté « 28 fichiers » pendant vingt minutes, le 16/08/2026 — le compte des seuls modules IriX, pris pour le total. Corrigé en dérivant les
repo:du script au lieu de les compter de tête. Dans le point même qui reproche au projet ses chiffres écrits à la main. Le compte se mesure :grep -c 'repo:'sur la section, jamais de mémoire.
Phase 2 — ✅ FAITE le 16/08/2026. _download compare l'empreinte du fichier reçu à celle publiée, et refuse sans poser le fichier en cas d'écart. Éprouvé pour de vrai contre le site : fichier sain accepté, empreinte faussée refusée et fichier absent, fichier hors manifeste posé avec avertissement, site injoignable sans blocage. ⚠ Le banc a trouvé un défaut dans le correctif lui-même dans l'heure : le cache du manifeste était rendu sur le seul critère « non vide », donc un JSON corrompu était accepté pour toute la durée du script.
Phase 3 — ✅ FAITE le 16/08/2026. L'empreinte attendue vient désormais du manifeste, pas d'un téléchargement antérieur. ⚠ Elle n'est interrogée que si l'URL est celle du chemin — le module a un repli qui va chercher certains fichiers ailleurs, et comparer alors accuserait à tort. ⚠ Le repli sur l'ancienne comparaison est conservé (des fichiers surveillés ne sont pas au manifeste) mais nommé pour ce qu'il est au journal. ⚠⚠ Et le silence est rompu : l'absence d'empreinte était tue, elle est maintenant dite.
Ce qui reste pour fermer le point : le constat en vraie installation tierce — un module volontairement altéré sur le site doit être refusé, et la même installation aboutir sans encombre quand rien n'est altéré. Les deux moitiés comptent.
⚠⚠ Les six pièges, tous MESURÉS
- ⚠⚠ Vérifier APRÈS le dépouillement accuse un fichier sain. Démontré le 16/08 sur
irix-alerte.tcl: brut02846c61…= concorde avec le manifeste ; après passage du dépouilleur00c70876…= diverge. 19 261 → 6 639 octets, 236 lignes retirées. Et comme le dépouillement dépend de.git.irix.depouiller, deOVERTCHAT_SANS_DEPOUILLE=1et de la présence detclsh, trois installations saines produisent trois empreintes différentes. La vérification va dans_download, avant transformation. - ✅ Le cache du manifeste ouvrait une fenêtre de refus de cinq minutes après chaque déploiement — corrigé en 4.18.7, après l'avoir provoqué. ⚠ À ne pas rouvrir en « optimisant » ce cache.
- ⚠ Rendre
_upd_apply_filestrict bloquerait toute première application. Un fichier légitimement absent en local est classé « mise à jour disponible » : c'est le mécanisme d'installation initiale. - ⚠ Le repli
Bootstrap/<rel>de_upd_resolve_download_urlchange le chemin réel sous le chemin demandé — une empreinte indexée sur$relcomparerait deux fichiers différents. - ⚠ Le mode hors ligne est un objectif explicite (
_find_local_archive), et les composants tiers ne sont pas au manifeste (Servers/etBuilder/: 0 entrée, mesuré). Une vérification imposée dans_fetch_sourcescasserait l'installation hors ligne. - ⚠ Pas de
readdans un chemin non interactif. Le modèle de_site_install(« Installer quand même ? [o/N] ») est bon dans un assistant ; les commandes documentées comme utilisables encronn'ont pas de terminal.
⚠ Ce qu'il ne faut PAS faire
- ⚠⚠ NE PAS ouvrir la signature (GPG/minisign). Mesuré le 16/08 : aucune paire de clés du projet n'existe (la seule clé du trousseau est celle d'UnrealIRCd, tierce), la sauvegarde chiffre en symétrique,
minisignetsignifysont absents. Signer demanderait donc de créer et de protéger une clé privée — un nouveau secret permanent, alors que la phrase secrète des sauvegardes a été « le point le plus grave du projet » jusqu'au 15/08. Hors périmètre, et à dire explicitement plutôt qu'à laisser croire. - ⚠ NE PAS faire consommer
X-Content-SHA256en le présentant comme une vérification. L'en-tête est calculé par le processus qui sert l'octet : il détecte une troncature de transfert, rien d'autre. - ⚠ NE PAS remonter sur la page publique la phrase de
SECURITY.md(« l'empreinte vient du même serveur que le fichier »). Elle est vraie, mais c'est la propriété de toute distribution qui publie un SHA-256 à côté de son téléchargement. La dire là où personne ne la dit ferait conclure que ce projet est moins sûr que les autres, alors qu'il est au même niveau. Le correctif n'est pas d'alarmer sur ce que l'empreinte ne fait pas ; il est de cesser d'affirmer que les composants sont vérifiés.
⏪ CE QUI SUIT EST L'ÉTAT D'AVANT LA FERMETURE — PÉRIMÉ, gardé pour la trace. Le critère décrit ci-dessous a été tenu le 23/08/2026 — voir le constat en tête de point. Les deux moitiés ont été prises sur une Ubuntu 24.04 neuve — c'est exactement le critère que l'encadré ci-dessous réclame, mais il est énoncé 185 lignes plus bas que le constat qui le remplit. ⚠ Ne pas le relire comme du travail à faire. Un encadré « Pour fermer » périmé sous un titre ✅ est exactement ce qui fait rouvrir un point déjà clos.
| Pour fermer | Une installation tierce où un module d'IriX volontairement altéré sur le site est refusé par l'installateur, et où la même installation aboutit sans encombre quand rien n'est altéré. ⚠ Les deux moitiés comptent : un contrôle qui refuse tout est aussi inutile qu'un contrôle qui n'a jamais refusé. |
|---|
27. ✅ Le webchat avertit quand on vous cite — LIVRÉ et CONSTATÉ le 16/08/2026
Demandé par SerVuS : avertir la personne quand son pseudo est cité — « soit faire clignoter le salon quand il ne se trouve pas sur la page active, soit faire une notice ».
⚠ Mesuré : la détection de mention n'existe à aucun degré. Zéro occurrence de mention, highlight, Notification, document.title, visibilitychange, focus/blur dans tout Web/. Aucune comparaison entre le contenu d'un message et le pseudo : toutes les comparaisons à monNick portent sur l'auteur ou la cible d'un événement de protocole.
Ce qui existe déjà et sert de base : un compteur nonLus par salon, incrémenté uniquement sur PRIVMSG, rendu en pastille par majSalons(), remis à zéro par activer(). ⚠ Mais il ne distingue pas le bruit d'une mention, et « non actif » y veut dire « pas l'onglet interne affiché » — un visiteur dont le navigateur est minimisé n'a aucun signal.
⚠⚠ Les six pièges mesurés, tous vérifiés dans le code :
- Aucune notion de frontière de mot n'existe dans
chat.js. Le seul motif de recherche est unindexOfnu : repris tel quel, « Al » sonnerait sur salut, alors, canal. - La casse : les comparaisons d'identité de
traitesont strictes (deP === monNick), alors que le reste du fichier passe parcleDe()(toLowerCase). IRC, lui, est insensible à la casse. - L'écho local :
envoieMessageappellelignePane(s,'moi',monNick,…)avec le texte qu'on vient de taper. Une détection posée danslignePanesonnerait sur nos propres messages où l'on se cite. - Les services citent systématiquement le destinataire (« Ce pseudo est enregistré », réponses de WHOIS). Sans garde, l'onglet « Activité serveur » sonnerait à chaque identification.
- Le rejeu d'historique passe par le même
lignePaneque le direct, etlignePanehorodate avec l'heure courante en ignorant le champhrenvoyé par le site. Un rejeu ferait sonner des mentions vieilles de jours. - Le pseudo change (collision
433→X_,NICKforcé par les services). Une détection qui capturemonNickune seule fois cesserait de marcher ;monNickest aussi temporairement faux entre un433et le001qui le confirme.
⚠ Et le vocabulaire CSS est CLOS (Web/tests/test-vues-site.php). Le fil a huit types (msg, moi, prive, autre, notice, info, err, hist), tous ne changent que la couleur du texte — aucune règle de fond ni de bordure —, et les cinq variables de couleur sont déjà toutes assignées. ⚠⚠ Aucun banc ne couvre ces huit types : test-vues-site.php relève les classes littérales, or lignePane écrit 'chat-ligne ' + type où type est un argument. Une neuvième classe s'ajouterait sans faire rougir personne, et sans style si on oubliait la feuille.
✅ LIVRÉ ET CONSTATÉ le 16/08/2026 (4.19.0), en production. Le constat a été obtenu sans se connecter au réseau public : le WebSocket du navigateur a été remplacé par un faux, piloté à la main, dans lequel de vraies lignes IRC ont été injectées. Le client parcourt ainsi son chemin complet — traite → lignePane → majSalons — sans qu'un octet parte au réseau.
| Message injecté vers un salon non regardé | Classe rendue | |
|---|---|---|
| « il fait beau aujourd'hui » | msg | ordinaire |
| « Paul tu viens ? » | cite | ✅ mention détectée |
| « paulette est arrivée » | msg | ✅ sous-chaîne ignorée |
OverNick : « Paul votre pseudo est enregistré » | msg | ✅ service épargné |
Paul : « Paul c'est moi qui parle » | moi | ✅ écho de soi épargné |
Et la colonne : le salon non regardé porte la classe cite, sa pastille passe en chat-nonlus cite avec l'infobulle « Un message vous cite » ; le salon regardé n'a rien. Les trois gardes tiennent en service, pas seulement au banc.
⚠ Ce qui reste à constater : que le rejeu d'historique ne produise aucune mention. Il ne peut pas en produire par construction — chargeHistorique appelle lignePane directement, en type hist, sans passer par le cas PRIVMSG où vit la détection —, mais cela n'a pas été vu. Le constat demande un compte identifié, l'historique étant réservé.
28. ✅ Le chat se ferme quand on clique ailleurs sur le site — CONSTATÉ FERMÉ le 23/08/2026 par SerVuS
Demandé par SerVuS : ouvrir le chat dans un nouvel onglet du navigateur à la connexion, « car si on clique sur une autre page du site, ça ferme le site — pas cool ». Et, par protection, si un chat est déjà ouvert, une nouvelle tentative de connexion depuis la page Chat doit être bloquée, « pour éviter X connexions, pour éviter le flood en connexion ».
Le défaut est réel et sans détour : le webchat vit dans la page. Toute navigation le détruit, ce qui coupe la connexion IRC.
✅ UN DÉFAUT DÉJÀ PRÉSENT A ÉTÉ TROUVÉ ET CORRIGÉ EN PRÉPARANT CE POINT (16/08/2026) — et il produisait exactement ce que SerVuS veut éviter : deux connexions pour un seul visiteur, sans le moindre geste fautif.
Le réseau coupe. ws.onclose appelle finConnexion(), qui réaffiche le formulaire, puis planifieReconnexion(). Le visiteur voit le formulaire revenu, reclique « Entrer », une socket s'ouvre. Quelques secondes plus tard le recoTimer toujours armé tire à son tour : connecteMoi écrase la référence ws, et la première socket reste ouverte pour toujours, son onclose ne pouvant plus être déclenché par personne.
connecteMoi() coupe désormais les fils de la socket précédente avant de la fermer — sinon son onclose planifierait une reconnexion vers la session qu'on remplace — puis annule la reconnexion en attente, sauf en reprise.
Constaté en production avec un faux WebSocket qui compte les sockets : coupure 1006, reclic sur « Entrer », attente que le recoTimer tire → 3 sockets créées, 2 fermées, une seule vivante.
❌ CE QUI SUIVAIT ÉTAIT FAUX POUR CETTE MACHINE, ET VRAI POUR TOUTES LES AUTRES. Le paragraphe affirmait que set::websocket-proxies est « vide par défaut », donc que tous les visiteurs sont vus comme 127.0.0.1, se partagent un plafond « de 3 à 5 », et que « le quatrième visiteur peut être refusé » — le tout présenté comme une décision d'exploitation à trancher. Mesuré le 16/08/2026 :
| Affirmation | Mesure |
|---|---|
websocket-proxies vide | faux ici — /home/servus/overtchatd/conf/overtchat.conf (hors dépôt, c'est bien celui que charge le processus) porte websocket-proxies "127.0.0.1", posé le 03/08/2026 vers 22:24 |
visiteurs vus comme 127.0.0.1 | faux — journal du serveur : « adresse relayée par 127.0.0.1 adoptée — 192.168.1.254 », 3 fois aujourd'hui, 10 de plus la veille, corrélées à la seconde aux 101 Switching Protocols d'Apache |
| plafond partagé « de 3 à 5 » | faux — chaque visiteur est compté sous son adresse, allow { ip *@*; } → maxperip 3 |
| « le quatrième peut être refusé » | aucune trace — zéro ligne Trop de connexions dans les journaux du serveur |
| « décision d'exploitation » | non — il n'y a rien à arbitrer ici |
⚠⚠ MAIS LE DÉFAUT EXISTE, ET IL EST CHEZ LE TIERS. Le correctif du 03/08 a été appliqué à la production et jamais au modèle distribué : Servers/overtchat-hybrid/conf/overtchat.conf.example ne portait pas la directive, et Bootstrap/overtchat-setup.sh ne la pose jamais (0 occurrence). Or Servers/overtchat-hybrid/contrib/install-service.sh fabrique la configuration d'un tiers en copiant ce modèle. Chez lui, donc :
- aucune adoption d'adresse — tous les visiteurs vus comme
127.0.0.1; - le bloc
allow { ip *@*; class clients; }du modèle portemaxperip 5; - le sixième visiteur simultané est refusé, alors qu'il est seul chez lui ;
- et un bannissement par adresse frappe tout le monde ou personne.
✅ CORRIGÉ le 16/08/2026 : la directive est entrée au modèle, avec ce qui la rend sûre écrit à côté. Ce n'est pas un arbitrage entre « adresse partagée » et « CGNAT » : sans elle, tous les visiteurs partagent un compteur, ce qui est strictement pire que le pire cas du CGNAT.
⚠ Ce qui la rend sûre, et qu'il ne faut pas défaire : le bloc listen du port 8000 n'écoute que la boucle locale — dans le modèle comme en production, vérifié identique. Ce n'est pas la liste qui protège, c'est que le port n'est pas publié. Y mettre *, ou une adresse non locale, donnerait à quiconque le pouvoir de s'attribuer n'importe quelle identité et de contourner un bannissement.
⚠ UNE ANOMALIE INVERSE, MESURÉE, RESTE OUVERTE : le webchat échappe totalement au limiteur de rafales. throttle_exempt() dispense 127.0.0.1, et le limiteur s'exécute à l'accept(), avant que l'adresse du visiteur soit connue. Cela ne se corrige donc pas au même endroit, et surtout pas en retirant la boucle locale de la dispense : IriX et les quatre OverOp s'y connectent tous. C'est permissif, non restrictif — l'inverse de ce que ce point annonçait.
🔴 RESTE À TRANCHER — comment le modèle corrigé atteint un tiers. Components/Serveur/lib/overtchat-hybrid-2.0.0.tar.gz porte bien conf/overtchat.conf.example, mais elle est gitignorée, produite à la main par Tools/faire-archive-hybrid.sh, et absente de l'inventaire servi. servers.json porte source_url: null et sources_locales: "Servers/overtchat-hybrid" : l'installateur attend les sources dans le dépôt local. Le correctif atteint donc un tiers qui obtient le dépôt, et pas un tiers qui recevrait cette archive. La régénérer est un geste à part — elle ne se produit pas toute seule et rien ne le rappelle. (Refaite le 17/08 à 10:05, 9 contrôles verts, correspondant au commit du correctif du point 33.)
⚠⚠ ET C'EST PIRE QUE « PAS À JOUR » — MESURÉ LE 17/08/2026. Servers/ n'est distribué par AUCUN canal, l'archive comprise :
| Canal | Porte Servers/ ? |
|---|---|
archive du site (distrib_archive_site()) | ❌ elle emporte Web, Tools, Config/identity.json.example et les 5 fichiers de DISTRIB_RACINE_SITE |
manifeste servi (distrib_manifeste() / distrib_inventaire()) | ❌ zéro entrée sous Servers/ |
_download() — le seul téléchargeur fichier par fichier | ❌ il ne suit que git.irix.files et git.overop.files |
_find_local_archive() | ❌ il cherche dans des répertoires que l'archive du site ne crée jamais |
Conséquence, et elle change la nature du point : chez un tiers passé par le site, $BASE_DIR/Servers/ n'existe pas, le test Makefile + src/ de overtchat-setup.sh est donc faux, _srv_fetch_sources ne trouve rien et l'installation d'Overtchat-Hybrid échoue — le tiers tourne nécessairement sur un autre IRCd, UnrealIRCd par défaut, seul à porter un source_url externe. Il n'exécute donc pas ce code, et aucun correctif du serveur ne le concerne.
⚠ Une page du site affirmait le contraire au visiteur — Web/site/installation.php lui disait que « son code est livré avec le projet […] et l'installateur le compile sur place », vrai du dépôt et faux de ce que ce site livre. Corrigé le 17/08. La décision reste entière : soit Servers/ entre dans ce qui est distribué, soit on assume qu'Overtchat-Hybrid ne s'installe que depuis le dépôt — mais alors tout ce qui est écrit ailleurs doit le dire, et c'est ce qui manquait.
🔧 Le serveur a été RECOMPILÉ ET REMIS EN SERVICE — 28/08/2026
Sur accord explicite de SerVuS. Ce n'est pas la décision ci-dessus, qui reste entière : c'est la première fois qu'un correctif du serveur C atteint la production dans cette série.
Ce qui a été corrigé : le
PROTOCTLannoncé aux serveurs pairs était périmé — il y manquaitJ(groupe C) etA(groupe D), présents dans la table et dans le005envoyé aux clients. Le même serveur disait donc deux choses différentes selon son interlocuteur, etPROTOCOL.mddocumentait la version fausse. ⚠ Le mode d'échec dormait : aucun secondovertchatdn'est lié, et un pair qui reçoit une liste incomplète ne sait pas quelle arité donner àJ.⚠⚠ La leçon était déjà écrite dans le même fichier.
chanmode_all_letters()dérive la liste du004de la table, avec sa raison en commentaire — « deux modes ajoutés le 28/07/2026 y manquaient ». Elle n'avait jamais été portée aux deux voisines. Une doctrine appliquée à un endroit et pas au voisin : c'est le défaut de famille du projet, et c'en est le quatrième exemple de la journée.Ce qui a été fait, dans l'ordre : sauvegarde du binaire en service avant le geste, avec la commande de retour arrière écrite ;
make cleanpuismake(34 fichiers, zéro avertissement) ; les 17 campagnes du serveur, qui montent leurs propres serveurs surbuild/et ne peuvent pas atteindre celui qui tourne ; arrêt d'IriX puis du serveur ; pose ;configtestdepuis le même répertoire et le même compte que l'unité systemd ; redémarrage.Constaté à 100 s — le contrôle tardif n'est pas du zèle, ce projet a vu un service mourir à 89 s.
NRestarts=0sur les deux services, empreinte du binaire en service identique à celle compilée, aucune erreur au journal. ⚠⚠ Et le005a été LU sur une vraie connexion, pas déduit du source. IriX avait retrouvé son lien ULine et traitait déjà le trafic.⚠ Ce que cela ne change PAS :
Servers/n'est toujours distribué par aucun canal. La production compile depuis son propre clone ; un tiers, non. La décision ci-dessus reste ouverte, entière.📌 Ce qui reste à faire sur ce point précis : les trois chaînes sont désormais alignées, pas dérivées. La vraie correction est de les construire depuis la table, comme le
004.Web/tests/test-chanmodes.phpempêche la dérive de revenir en silence ; il ne la rend pas impossible.
✅✅ LES TROIS POINTS DURS CI-DESSOUS SONT ÉCRITS DEPUIS LE 16/08/2026, et ce point ne le disait pas. Mesuré le 23/08 : le commit « le chat s ouvre dans son onglet, et refuse d en ouvrir deux » les a tous les trois traités le soir même, et le registre n'a jamais été remis à jour — il a décrit pendant une semaine un état vieux de trente-sept versions. Ce qui suit est donc conservé pour le raisonnement, pas comme état courant :
- (1) l'ouverture d'onglet — résolue par un lien et non par
window.open(), pour la raison exacte qu'expose le point :site_nav_cible()posetarget="_blank"sur l'entrée « Chat », et un lien n'est jamais bloqué. Aucune page « chat seul » n'a été nécessaire, donc aucun des périls décrits ici. - (2) le verrou expire —
overtchat:chat-actifenlocalStorage, durée de vie 15 s renouvelée toutes les 5 s. C'est un signe de vie, pas un drapeau : un onglet fermé brutalement libère la place au bout de 15 s. - (3) la reprise ne se bloque pas elle-même — le verrou est reposé par
connecteMoi(), reprise comprise, et n'est consulté qu’à la soumission du formulaire. Une reconnexion automatique renouvelle donc le sien au lieu de s'y heurter.
⚠ Ce verrou est un confort, pas une garde, et le code le dit : localStorage est cloisonné par profil. Deux navigateurs, une fenêtre privée ou deux machines ne se voient pas. Ce qui limite réellement les connexions est ailleurs — maxperip et le plafond ASV par adresse.
⚠⚠ LA MOITIÉ QUI MANQUAIT VRAIMENT, ET QUE CE POINT NE NOMMAIT NULLE PART — corrigée le 23/08/2026. Sortir le chat dans son onglet ne protege que le premier clic. Une fois dans cet onglet, la page affichait l'en-tête et le pied complets du site : barre de navigation, menu replié, quatre groupes du pied, logo, et trois liens du corps — plus de vingt liens ordinaires, dont chacun détruisait le document. Or le document est le seul propriétaire de la socket : ni SharedWorker, ni fenêtre parente, ni relais PHP ne lui survivent. Cliquer « Charte » depuis le chat perdait exactement la conversation que le correctif du 16/08 prétendait sauver.
⚠ L'attribut n'a pas été écrit vingt fois, et c'est le fond du correctif : le pied porte ses liens en dur, la navigation les produit en boucle, chat.php en ajoute trois. Trois écritures valent trois vérités, et le lien ajouté dans six mois n'en connaîtrait aucune. La page du chat relit désormais sa propre sortie en une passe unique — site_liens_en_onglet(), armée par site_header() et rendue par site_footer(). Les ancres, mailto: et ce qui porte déjà un target sont laissés tels quels.
⚠⚠ ET RIEN NE GARDAIT TOUT CELA. test-nav-site.php ne contenait ni _blank ni site_nav_cible : le correctif entier pouvait disparaître d'une ligne sans qu'un seul banc rougisse. test-chat-onglet.php (23/08) rend la page et relit les liens qui en sortent — chercher le mot _blank dans un fichier prouverait qu'on l'a écrit, jamais qu'il agit. Huit mutations essayées, huit attrapées, dont la contre-épreuve qui sépare « la règle ne s'applique nulle part » de « la règle s'applique partout » — les deux étaient vertes sans elle.
✅ MESURÉ DANS UN VRAI NAVIGATEUR, SUR LA PRODUCTION, LE 23/08/2026 — page servie par https://service-overtchat.hd.free.fr/?p=chat, pied de page portant v4.19.39 :
| Page | Liens | Restent dans l'onglet | Sans noopener |
|---|---|---|---|
?p=chat | 53 sortants | 0 | 0 |
?p=accueil | 56 en tout | — | — |
⚠ Et la contre-épreuve, qui est la moitié qui compte : sur ?p=accueil, trois liens seulement sortent en onglet neuf, et ce sont les trois qui mènent au chat. Aucun autre. Une règle qui ne s'applique nulle part et une règle qui s'applique partout auraient toutes deux passé le premier tableau ; seule celle-ci les sépare, et « partout » aurait cassé la navigation du site entier.
✅✅ FERMÉ LE 23/08/2026. QUATRE MODÈLES ONT ÉTÉ ESSAYÉS DANS LA JOURNÉE, et les trois premiers étaient défendables au moment où ils ont été posés. Le récit compte plus que le résultat, parce que chaque passage a été provoqué par une mesure, jamais par une relecture :
| Modèle | Ce qui l'a fait tomber |
|---|---|
le chat sort en _blank | la moitié chat → site n'existait pas : depuis le chat, vingt liens coupaient encore la conversation |
| tout sort du chat, en une passe | il manquait l'entrée : on pouvait ouvrir deux chats |
| le chat vise un onglet nommé | ⚠⚠ un lien qui vise un onglet existant le NAVIGUE — SerVuS : « ça ferme la connexion et revient au panneau de pseudo ». Le remède était pire que le mal. |
| ✅ on entre sur place, tout le reste sort | — |
⚠⚠ LA LEÇON EST CELLE-CI : le troisième modèle était VERT AU BANC quand il a été livré. Dix-neuf constats, douze mutations attrapées, et il cassait ce qu'il prétendait protéger. Aucun banc ne pouvait le voir : il faut deux onglets et un vrai clic pour qu'un lien nommé navigue sa cible. Ce qui l'a trouvé, c'est SerVuS en s'en servant, un quart d'heure après la livraison.
⚠ Les trois demandes de SerVuS de cette journée ne sont pas indépendantes, et l'ordre compte : la confirmation avant fermeture autorise l'entrée sur place. Sans elle, un retour en arrière depuis le chat détruirait la conversation sans un mot.
⚠ Et un défaut de fond a été trouvé en écrivant la confirmation : beforeunload envoyait le QUIT avant que la question ne soit posée. Répondre « rester » aurait gardé la page sur une connexion déjà close par nous-mêmes — fil affiché, mort, et rien pour le dire. Le QUIT est descendu dans pagehide.
⚠ Un trou de quinze secondes existait aussi : le verrou survit à son onglet le temps de son échéance, et la bascule ouvrait alors une page blanche. Le verrou et l'onglet sont deux choses, et l'un peut mentir sur l'autre.
⚠⚠ Le banc a menti trois fois dans la même séance, et la MUTATION l'a dit les trois fois — jamais la relecture. Deux constats interrogeaient une page produite par la fonction même qu'ils vérifiaient ; un troisième cherchait des lignes qui restent écrites quand on neutralise la condition qui les commande. Un constat doit exiger la CONDITION, pas ses effets — et avoir appris la leçon trois constats plus haut n'a pas suffi à me la rappeler.
État final : 40 constats, 22 mutations essayées, 22 attrapées, en 4.19.43.
✅✅ CONSTATÉ PAR SerVuS LE 23/08/2026, ET SUR PLUSIEURS PAGES — pas seulement celle qu'on lui avait indiquée : « chaque page ouvre bien en onglet pour laisser la page chat ouverte », et « le chat tourne toujours ». C'est la moitié du critère qu'aucune mesure ne remplace — la mesure au navigateur prouvait que la page ne peut plus être déchargée par un lien ; elle ne prouvait pas qu'on avait vu le fil rester en place.
⚠ Et le fait qu'il ait essayé PLUSIEURS pages compte : le correctif ne pose pas l'attribut lien par lien mais en une passe sur la sortie entière. Un constat sur un seul lien n'aurait pas séparé « la passe fonctionne » de « ce lien-là était déjà traité ».
⚠⚠ DEUX MOITIÉS DU CRITÈRE RESTENT, ET LE POINT NE SE FERME PAS SANS ELLES. Ce sont les deux que la protection contre le flood ajoutait à la demande d'origine :
- la seconde tentative est refusée — un second onglet sur
?p=chat, formulaire rempli, « Entrer » : le message doit renvoyer à l'onglet déjà ouvert ; - ⚠ le verrou se libère tout seul — onglet fermé brutalement, ~20 s d'attente, nouvelle entrée : elle doit passer. C'est la plus importante des deux : un verrou sans échéance interdirait toute reconnexion définitivement après un plantage de navigateur — le péril nommé au point dur n°2, dont personne n'a encore vu la parade agir.
⚠ Le code des deux est écrit et lu (overtchat:chat-actif, 15 s renouvelées toutes les 5 s), et cela ne vaut pas constat — c'est la règle de ce registre.
⚠ Ce qui suit décrit l'état d'avant le 16/08, conservé pour le raisonnement :
- ⚠⚠ L'OUVERTURE D'ONGLET SE HEURTE À L'ARCHITECTURE, et c'est le point le plus dur. Mesuré : la dernière instruction encore synchrone avec le clic est le
fetch('?p=asv')lui-même. Tout ce qui suit — y comprisconnecteMoi()— vit dans un.then, donc hors du geste, où unwindow.open()est bloqué comme une fenêtre publicitaire.
Et l'on ne peut pas simplement ouvrir avant : à cet instant on ne sait pas encore si le site accepte l'ASV. L'ordre « contrôler d'abord, connecter ensuite » est une décision de sécurité écrite en toutes lettres dans chat.js — « ouvrir d'abord et contrôler ensuite laisserait entrer pendant le temps de la réponse, ce qui viderait le contrôle de son sens ».
⚠ Un simple target="_blank" échapperait au blocage, mais un lien ne transporte pas le formulaire : ni le pseudo, ni l'ASV, ni surtout le mot de passe — le faire voyager dans une URL contredirait frontalement l'invariant du fichier (« le client ne s'identifie jamais tout seul »).
⚠ Et le chemin n'est pas libre : index.php rend 404 sur toute URL autre que / et /index.php, tandis que chat.js fait tous ses appels en relatif (?p=asv, ?p=chat-hist…). Une page « chat seul » doit donc rester un ?p=, et reproduire tout le gabarit — la table el de chat.js déréférence une vingtaine de nœuds sans garde : un gabarit allégé qui en omet un plante au chargement, silencieusement pour le visiteur. 2. ⚠⚠ Le verrou doit EXPIRER. Un onglet fermé brutalement, un navigateur qui plante, et un verrou sans échéance interdit toute reconnexion — définitivement. Il faut un signe de vie renouvelé et une échéance courte, pas un drapeau posé une fois. 3. ⚠ La reconnexion automatique ne doit pas se bloquer elle-même. planifieReconnexion rappelle connecteMoi(…, reprise=true) : si le verrou ne distingue pas une reprise d'une nouvelle connexion, le chat se condamnerait à la première coupure réseau.
⚠ Ce point et le 27 touchent le même fichier (Web/public/assets/chat.js) et la même zone d'état par salon. Les traiter séparément ferait deux fois le même travail de reprise. ⚠⚠ Et chat.js porte un défaut documenté, trouvé dans un vrai navigateur le 09/08/2026, que « aucun banc ne pouvait voir » : toute écriture d'état par salon doit décider explicitement de son sort en reprise, comme le font déjà histCharge, rejoint, presents, modes.
| ✅ CONSTATÉ FERMÉ | Le 23/08/2026, par SerVuS, dans un vrai navigateur, sur le modèle définitif — quatre gestes, tous vus : cliquer « Chat » depuis l'accueil reste dans l'onglet · depuis le chat, « Commandes » ouvre un onglet neuf et la conversation reste intacte · depuis cet onglet, « Chat » bascule sur la conversation sans la recharger · et F5 sur un chat connecté demande confirmation. ⚠ Les deux gestes du verrou avaient été constatés plus tôt le même jour, sur le modèle précédent : refus de la seconde entrée, et libération seule après fermeture brutale. |
|---|
20. ✅✅ Six actions du panel étaient MORTES — CLOS le 16/08/2026 à 11:11:06
⚠ Il reste ici tant que personne n'a vu un bouton faire ce qu'il annonce. Le code est écrit, le banc est vert, les six mutations mordent — et cela ne vaut pas constat. C'est la règle du carnet, et elle a déjà évité trois fermetures creuses.
Signalé par SerVuS le 16/08/2026, depuis la page : « Administration > Botnet, la partie pour lier les eggdrop met : action inconnu ».
Le fait : toute requête POST du panel traverse un portier (Web/public/admin.php:359) — if (!array_key_exists($action, ACTION_LEVELS)) → « Action inconnue. ». Six actions étaient traitées par un case et absentes de cette table. Leur code était donc mort depuis qu'il était écrit.
| Action morte | Page |
|---|---|
botnet · botnet_add · botnet_del · botnet_enable · botnet_reset | Botnet — la page entière |
proto | ULines — le dialecte d'un lien |
⚠⚠ LE DÉFAUT ÉTAIT PLUS LARGE QUE LE BOUTON SIGNALÉ, et c'est la leçon. Un seul bouton a été rapporté ; le croisement en a trouvé six. proto n'avait jamais été signalé par personne — on ne change pas de dialecte tous les jours. Un bouton rarement cliqué reste mort sans que rien ne le dise. Ce n'est pas l'usage qui trouve ce genre de trou, c'est un croisement.
⚠ C'est le cinquième trou « à la jointure » du projet — du code juste des deux côtés, et personne pour les relier. Les quatre précédents : le module irix-edition.tcl que rien ne chargeait, le transport de botnet écrit d'un seul côté, test-botschan sur un salon jamais balayé, le scan quotidien qui ne partait pas.
✅ CE QUI EST FAIT, en 4.17.5 — les six entrées, aux niveaux tranchés par SerVuS et non devinés ; et surtout Web/tests/test-panel-actions.php, qui dérive trois listes et n'en énumère aucune : les entrées d'ACTION_LEVELS, les case du switch, et ce que les pages postent réellement. Il les croise dans les trois sens, garde le portier lui-même, et porte trois garde-fous contre le vert-à-vide. 10 constats, 6 mutations, 6 attrapées.
⚠ Ce que le banc NE fait PAS : il ne lit rien de ce que fait une action ni du niveau qui lui est donné. Il constate qu'elle est joignable, rien de plus.
✅ LA MOITIÉ PANEL EST CONSTATÉE — 16/08/2026 à 10:49. SerVuS a cliqué « Lier », le panel a répondu « en attente de traitement », et la table portait action_pending = link à 10:50:11. Le panel pose donc l'action, ce dont il était incapable la veille.
⚠⚠ ET CE CONSTAT A OUVERT LE POINT 21 DANS LA MINUTE. L'action était bien posée, bien lue par le bot… et refusée à chaque cycle. Le trou en cachait un second, comme pour irix-edition.tcl le 15/08. Voir ci-dessous.
⚠ La trace n'est dans AUCUN des trois journaux — elle est en base, dans la table ${t}_botnet_log, celle qu'affiche « Activité du botnet ». J'ai failli conclure au silence en cherchant dans IriX.log, IriX-sql.log et journalctl : les trois étaient muets, et le mécanisme fonctionnait. Compter trois journaux ne suffit pas quand un quatrième endroit existe.
| Pour fermer | ✅ RIEN — CLOS le 16/08/2026 à 11:11:06, en même temps que le 21 et par le même constat : le panel a monté un lien de botnet. ⚠ proto (dialecte d'un lien ULine), la sixième action ressuscitée, n'est pas constatée pour autant — on ne change pas de dialecte pour éprouver un correctif. Elle est déclarée, traitée, et tenue par test-panel-actions.php ; cela ne vaut pas constat, et il faut le dire. |
|---|
21. ✅✅ Le bot REFUSAIT toute action du panel — CLOS le 16/08/2026 à 11:11:06
⚠ CONSTATÉ FERMÉ, et par la seule preuve que ce point acceptait : un lien monté depuis le panel. SerVuS a rechargé IriX depuis le site, puis cliqué « Lier ».
11:08:52 Rehash Eggdrop — OK (déclenché depuis le panel)
11:08:53 bind msg 'reload' -> ::soc::cmd_reload (les modules sont re-sourcés)
11:09:07 BOTNET : boucle d'observation redémarrée
11:10:06 attempt OverOp-T « link » demandé par le panel
11:11:06 link OverOp-T lien constaté
OverOp-T est passé à linked, zéro échec, et plus un seul refused.
⚠ Le rechargement n'a coûté ni redémarrage ni coupure : le bouton « Recharger IriX » pose le drapeau reload, que ::soc::admin::do_reload consomme par un source IriX-sql.tcl — lequel re-source tous les modules. Les 70 à 95 s d'absence d'un systemctl restart ont été évitées. ⚠ Cette information était déjà dans mes fiches et je ne l'avais pas lue : j'allais proposer un redémarrage.
Trouvé le 16/08/2026 à 10:52, une heure après le point 20, et EN REGARDANT AGIR.
Le fait : ::soc::botnet::traiter_actions sélectionne les lignes dont action_pending n'est pas none, puis soumettait l'enregistrement à action_verdict — dont la garde anti-doublon regarde ce même action_pending. L'action se refusait elle-même, à chaque cycle.
10:49:32 refused OverOp-H « une action est déjà programmée : link »
10:50:32 refused OverOp-H « une action est déjà programmée : link »
⚠ Aucune action du panel n'a jamais pu aboutir. Le lien monté le 15/08 à 21:30 l'avait été par .link à la main dans la console — le seul chemin qui marchait. Le chemin du panel était mort plus haut, au portier du point 20 : personne ne pouvait donc atteindre ce second défaut.
⚠⚠ DEUX QUESTIONS POSÉES À UNE SEULE PROCÉDURE. « Peut-on POSER cette action ? » et « peut-on EXÉCUTER celle qui est posée ? » n'ont pas la même réponse. La garde anti-doublon est juste pour la première et interdit tout pour la seconde.
⚠⚠ ET LE BANC ÉTAIT VERT DESSUS — c'est ce que ce point apporte de plus utile. test-botnet.tcl vérifiait « une action déjà programmée bloque », et il avait raison. Une règle juste posée à la mauvaise question ne rougit nulle part. Aucun banc, aucune relecture ne l'attrape : seul un geste réel le montre. C'est la meilleure raison connue de cliquer soi-même sur ce qu'on vient d'écrire.
✅ CE QUI EST FAIT, en 4.17.6 — action_verdict prend en_traitement (0 par défaut : aucun appelant existant ne change), qui lève cette seule garde ; traiter_actions l'appelle avec 1. Six constats ajoutés au banc (69 vérifications), tenant les deux sens et éprouvant les trois autres gardes en traitement. 3 mutations, 3 attrapées.
⚠ Le constat le plus utile du lot lit le corps réel de traiter_actions : sans lui, retirer le 1 remettrait le défaut en laissant les cinq autres verts. « Garder la porte en regardant la serrure », déjà payé le 14/08.
| Pour fermer | ✅ RIEN — CLOS le 16/08/2026 à 11:11:06. Le constat exigé était un attempt là où il n'y avait que des refused ; on a eu mieux, un lien monté. ⚠ Ce qui reste vrai et n'appartient pas à ce point : OverOp-H demeure unlinked et n'a aucun compte utilisable sur le botnet (point 2) — aucune tentative n'apparaît pour lui après le rehash. ⚠ Et le lien ne survit pas à un redémarrage : .link est un acte, pas un réglage. |
|---|
⚠⚠ CE POINT A OUVERT LE 22, ET C'EST SA MEILLEURE CONTRIBUTION. SerVuS a cru à une panne alors que le lien montait : il avait cliqué, la page n'avait rien montré de vivant pendant les 60 s du cycle. Une action différée sans retour visible se lit comme une action qui a échoué. Traité en 4.17.8 — voir ci-dessous.
⚠ La mise en garde de la veille vaut d'être relue, et elle vient de se vérifier une seconde fois : « ce qui vide cette section, c'est parfois seulement de ne pas avoir regardé ». Elle était vide le 15/08 à 19:06 — six actions du panel étaient déjà mortes à cet instant. Ce n'est pas un banc qui les a trouvées, c'est un utilisateur qui a cliqué.
18. ✅✅ irix-edition.tcl n'était chargé par personne — CLOS le 15/08/2026 à 19:06:20
⚠ CONSTATÉ FERMÉ le 15/08/2026 à 19:06:20, par la ligne que le bot en service a écrite de lui-même dans IriX/cmds/IriX-sql.log :
2026-08-15T19:06:20 [INFO] édition de cette installation : complete
Ouvert le 14/08/2026 à la clôture, corrigé le 15/08 en 4.17.0. Le corps est gardé tel quel : c'est lui qui dit pourquoi le défaut existait et ce qu'il a fallu écarter.
⚠⚠ Le premier redémarrage n'a RIEN prouvé, et il fallait le dire
À 19:03, IriX a été redémarré : PID neuf, lien monté en 5 s, service actif. Et aucun constat. irix-edition.tcl ne journalise rien au chargement — il ne définit que des procédures, comme la règle du projet l'exige. Un module muet ne peut pas prouver qu'il existe.
Les deux voies qui semblaient ouvertes étaient fausses :
| Voie | Pourquoi elle ne valait pas |
|---|---|
tclsh IriX/tests/_edition-lire.tcl rend complete | c'est le chemin aveugle qui a laissé passer le trou : il source le module lui-même |
la console d'IriX (.tcl info commands …) | elle exige le compte SerVuS (flags jlmnoptx), dont le mot de passe est haché |
✅ La sortie a été d'écrire ce qui manquait vraiment. Le point relevait que ::soc::edition n'était appelée par personne — module joignable et inutilisé. Journaliser l'édition au démarrage est la première question du chantier des éditions, utile en soi, et la preuve vient avec. ::soc::start_system, juste après load_config — donc le journal existe, et surtout pas au niveau du fichier, où l'appel précéderait la configuration.
⚠⚠ L'ABSENCE EST DITE, JAMAIS TUE. Une garde info commands qui sauterait en silence reproduirait le défaut à l'identique : le module pourrait redevenir orphelin sans que rien ne le signale. Les deux branches écrivent une ligne, et celle de l'échec est un WARN qui nomme sa cause et renvoie ici.
📌 La règle qui en sort, et elle dépasse ce point : on n'ajoute pas du code pour se prouver quelque chose ; on rend joignable ce qui manquait, et la preuve vient avec. Si la trace n'avait servi qu'au constat, elle aurait été un décor.
Le fait : IriX/cmds/irix-edition.tcl existait depuis le 14/08 — module juste, éprouvé, croisé par Web/tests/test-edition.php. Aucun source ne le chargeait. irix.conf source IriX-sql.tcl, qui source ses modules un par un ; celui-là n'y figurait pas. ::soc::edition n'existait donc pas dans le bot en service, et un appel aurait rendu « invalid command name » dans un catch, c'est-à-dire en silence.
Mesuré le 14/08 : zéro occurrence de « edition » dans IriX.log. Remesuré le 15/08 : zéro occurrence dans IriX-sql.tcl, et le croisement des 24 modules de cmds/ donne un seul orphelin — celui-là.
⚠⚠ LE TROU EN CACHAIT UN SECOND, trouvé en bouchant le premier. Le module manquait aussi aux deux manifestes de déploiement de Bootstrap/overtchat-setup.sh (git.irix.files et git.irix.modes.sql.modules). Le dépôt aurait marché ; une installation neuve serait morte au chargement, IriX-sql.tcl sourçant sans garde. Personne ne l'aurait vu avant une pose réelle.
⚠ Le hook pre-commit gardait déjà cette règle — et il ne pouvait pas jouer : il s'installe par clone, et il était absent du clone WSL le jour du constat. Une garde qui dépend d'un fichier non versionné n'est pas une garde. C'est pourquoi la règle est passée dans un banc.
⚠ ET AUCUN BANC NE POUVAIT VOIR LE PREMIER. test-edition.php atteint le Tcl par IriX/tests/_edition-lire.tcl, qui source le module lui-même — légitime pour éprouver la lecture, aveugle sur le chargement. Un banc qui source sa cible ne peut jamais dire si le bot, lui, la source. C'est le quatrième trou « à la jointure » du projet : un module juste, un banc vert, et personne pour les relier.
✅ CE QUI EST FAIT, en 4.17.0 — le source (section 6quaterdecies), les deux manifestes, et surtout IriX/tests/test-modules-charges.tcl : il dérive les trois listes et n'en énumère aucune, tient trois croisements (module chargé · source qui vise un fichier présent · module aux deux manifestes) et deux garde-fous contre le vert-à-vide. 8 mutations, 8 attrapées — dont la reproduction du trou lui-même.
⚠ Ce que le banc NE fait PAS : il ne lit rien de ce que fait un module ni de son bon fonctionnement. Il constate qu'il est joignable, rien de plus. Comme test-bindtime.tcl, c'est un point de passage obligé, pas une preuve.
| Pour fermer | ✅ RIEN — CLOS le 15/08/2026 à 19:06:20. Le constat exigé était de voir ::soc::edition répondre dans le bot en service, et il a eu lieu. ⚠ Ce qui reste vrai et vaut pour les prochains : chercher dans les deux journaux, IriX/cmds/IriX-sql.log et IriX/runtime/IriX.log — ici la ligne n'est que dans le premier, ::soc::log n'écrivant pas dans le second, et un grep sur le mauvais aurait fait conclure à l'échec. ⚠ Et tclsh IriX/tests/_edition-lire.tcl rendant complete ne prouvait rien : c'est le chemin aveugle qui a laissé passer le trou. |
|---|
⚠ Le redémarrage a été REPORTÉ délibérément le 15/08 à 18:00, et il faut dire pourquoi : le passage horaire de 19:00 portait la suspension de #membres, le retour d'IriX prend 70 à 95 s, et le prêt d'essai se périmait le 16/08 à 18:21 — il n'y aurait pas eu de seconde occasion. Le code a donc été déployé sans rechargement, comme le scan quotidien l'avait été le 14/08. Même motif, même raison. ✅ Le report a été payant des deux côtés : la suspension est partie à 19:00:00 et a été journalisée, et le redémarrage a suivi six minutes plus tard.
19. ✅✅ Le salon gardien #!Bots! — OUVERT ET CLOS le 15/08/2026
Demandé par SerVuS le 15/08/2026 : rassembler sur #!Bots! les Eggdrop qui servent au prêt de robot, les marquer +B, et montrer sur le site le nombre et le nom de ceux qui y sont connectés.
⚠ Consigne de SerVuS, et elle est structurante : les cinq sockets de service n'y vont pas. Ils portent +S et relèvent de la protection des services, qui est celle du serveur. #!Bots! ne concerne que les Eggdrop.
⚠ Le nom porte les DEUX points d'exclamation — #!Bots!, comme #!Overtchat! et #!Services!. La forme à une seule a été écrite d'abord, puis corrigée par SerVuS. Il ne vit qu'à un seul endroit du code, la constante BOTS_CHAN de Web/inc/botreg.php : le changement n'a coûté qu'une ligne et les 27 constats du banc sont restés verts au travers.
📌 LA CARTE DES SALONS — tranchée par SerVuS le 15/08/2026
| Salon | Rôle | État mesuré le 15/08 |
|---|---|---|
#!Overtchat! | accueil général | existe, IriX y siège |
#!Services! | IRCop — stockage des bots de service | existe, 6 utilisateurs |
#Csadmin | gardien d'IriX | existe, topic déjà « Salon Responsable IriX », modes inst |
#!Bots! | gardien des Eggdrop — +B + register | ❌ n'existe pas |
#accueil, #membres | essais | existent, IriX y siège |
Rien n'est à fermer : les cinq salons en base ont tous un rôle. ⚠ Deux faits que la mesure a ajoutés et dont aucun document ne parlait : deux prêts actifs (et non un), et une demande de salon en attente dans ${t}_chan_requests.
⚠⚠ POURQUOI « REGISTER » — la suspension du 15/08 en a donné la preuve
La formule de SerVuS dit « +B + register », et le constat de 19:00:00 a montré ce que le second coûte quand il manque. Le WARN nomme lui-même la cause :
prêt : #membres est accordé à OverOp-S, et OverOp-S n'y est PAS. […] un salon
réservé aux pseudonymes identifiés refuse les robots, qui ne le sont pas.
#membres porte +R. OverOp-S ne pouvait pas y entrer — le prêt n'a jamais eu la moindre chance d'être servi, et la suspension automatique, parfaitement juste, a sanctionné un robot qui n'y pouvait rien.
⚠ Ce n'est donc pas un détail de confort : tant que les robots ne sont pas enregistrés, tout salon +R leur est fermé, et un prêt accordé sur un tel salon se suspendra tout seul au bout de 24 h. C'est le point 10 — les pseudonymes des robots sans mot de passe — et on en connaît désormais le coût réel, mesuré, et non plus théorique.
✅ Ce que le serveur sait DÉJÀ faire — et qui n'était pas dans ce carnet
La « validation en réseau robot » existe, elle est en service, et elle est bien conçue :
/home/servus/overtchatd/conf/protection.conf
services = oui
robots = oui
IriX
⚠ La protection exige DEUX choses, jamais une — et l'en-tête de src/protection.c dit pourquoi : « Le mode +B se pose soi-même : n'importe quel client peut se déclarer robot. En faire un titre d'immunité offrirait au premier venu ce que les opérateurs eux-mêmes n'ont pas. » Il faut donc le mode +B porté ET le pseudonyme dans la liste. Le fichier s'écrit depuis le panel (?p=protection), jamais à la main.
Effets de +B, mesurés dans les sources : la protection ci-dessus (conditionnée à bots_on et à la déclaration), RPL_WHOISSPECIAL « is a bot on this network » au WHOIS, et le drapeau B dans le statut du WHO.
✅ Ce qui est fait — 4.17.0, 890f492 et f49196e
La lecture est en place au panel (?p=robots), et sans créer aucune source : elle lit ${t}_opmybot, que le bot tient déjà. Un second comptage aurait donné deux réponses à « qui est sur #!Bots! » — le défaut de la charge d'un robot, qui vivait à trois endroits (4.16.29 → 4.16.32).
⚠ La consigne « pas de services » est tenue PAR CONSTRUCTION, sans liste de noms. Ni _sync_presence (qui n'inscrit que le bot lui-même) ni _sync_robots (qui balaie botreg_pooshy) ne regarde les pseudo-clients. Une exclusion écrite à la main aurait été une liste de plus à tenir, donc à oublier. test-botschan.php le vérifie.
⚠⚠ UN DÉFAUT COMMIS ET CORRIGÉ DANS L'HEURE, à garder ici. La première version affichait « aucun robot » pour #!Bots!. Vrai sur la lettre, faux sur le fond : _sync_presence ne balaie que les salons de ${t}_channels, donc un #!Bots! absent de cette table n'est jamais regardé — la page l'aurait annoncé désert pour l'éternité, et la panne aurait été invisible. C'est le motif du scan quotidien qui ne partait pas (point 16). La page distingue désormais quatre états : non suivi · aucun robot · périmé · constaté, et « non suivi » est testé en premier — les deux premiers ont zéro occupant, celui qui teste en second ne se voit jamais.
Web/tests/test-botschan.php : 27 constats, 7 mutations toutes attrapées. Son constat le plus utile dérive la condition de suivi du Tcl au lieu de la recopier, et tient dans les deux sens — casser le PHP ou casser le Tcl rougit chacun son constat.
✅✅ EN SERVICE ET CONSTATÉ — 15/08/2026 à 20:11:00
Les cinq Eggdrop siègent sur #!Bots!, la page du panel dit « constaté — 5 robot(s) », et aucun écart n'est signalé.
20:11:00 OverOp-H/Q/S/T joined #!Bots!
20:12:00 BOTSTAT : « 1 salon(s) : #!Bots! » (« #accueil, #!Bots! » pour H)
⚠⚠ CE QUI BLOQUAIT N'ÉTAIT NI LE ROBOT NI SA CONFIGURATION — les deux étaient justes depuis le début, channel add #!Bots! posé et le salon en -inactive dans leur .chan. Le refus venait du serveur : conf/salons-interdits.conf est fermé par défaut (*:La création de salons est sur demande) et ne portait que six dérogations. #!Bots! n'en faisait pas partie.
⚠ L'asymétrie qui a rendu le défaut MUET : IriX y entrait par SJOIN — un lien de service, qui ne passe pas cette validation — quand les OverOp sont des clients ordinaires. Le salon semblait donc exister et fonctionner, et aucun refus n'était journalisé côté robot. Trois journaux lus, aucun ne portait le mot #!Bots! : c'est cette absence totale de trace, et non un message d'erreur, qui a fini par désigner le serveur.
Corrigé par une septième dérogation !#!Bots!, posée à côté de !#!Services! — son pair, autorisé pour la même raison —, avec sauvegarde, et appliquée par systemctl reload (SIGHUP) : aucune connexion rompue. Le serveur a confirmé : « 1 motif de salon interdit et 11 exceptions ».
⚠ Le mode +B était, lui, déjà bon : les quatre robots le portent (+ixB) depuis leur redémarrage de 20:02:34, appliqué par init-server — c'est-à-dire que la préparation des .conf avait fonctionné du premier coup.
✅ L'exemption du gardien n'a rien masqué — elle a signalé le vrai manque
Quatre lignes error sont apparues à 20:07, et leur libellé est la preuve que l'exemption fonctionnait :
OverOp-H error état … : absent de : #!Bots!
« absent de », et non « en trop ». #!Bots! était devenu un salon attendu ; les robots n'y étant pas encore, le mécanisme a signalé qu'ils manquaient — ce qui était vrai. Les lignes ont cessé d'elles-mêmes à leur entrée : zéro après 20:11.
⚠ C'est le meilleur résultat possible, et il valait mieux que celui visé : on craignait un faux « surplus » de 288 lignes par jour et par robot ; on a obtenu un vrai « manquant », qui s'est résolu seul. Une exemption bien placée ne rend pas un mécanisme aveugle, elle lui donne la bonne attente.
✅✅ LES ROBOTS SONT PROTÉGÉS — 15/08/2026 à 20:19:36
INFO Protection : services oui, robots oui, 5 robot(s) déclaré(s)
Les cinq portent +B et figurent dans protection.conf. protection_effet() le confirme un par un : « Protégé par la garde robots : déclaré ici ET porteur de +B. »
⚠ Écrit SANS accès au panel, mais PAS à la main. Le compte claude existe (niveau 100) et son mot de passe est haché — inutilisable. La sortie n'a pas été d'éditer le fichier, mais d'appeler protection_ecrire(), la fonction que le panel appelle : même composition, même écriture atomique (fichier temporaire puis rename), même validation des pseudonymes. Éditer le fichier aurait créé un second format, donc une seconde vérité — le défaut payé trois fois dans la seule journée du 15/08.
⚠ Le rechargement est resté un geste séparé, comme dans le panel : protection_appliquer() explique pourquoi — « recharger est un acte sur le service en production et on ne le fait pas à l'insu de celui qui a seulement coché une case ». Fait par reload (SIGHUP), et les 11 exceptions de salons sont restées chargées : aucune régression.
| Pour fermer | ✅ RIEN — le point 19 est CLOS le 15/08/2026. Les cinq Eggdrop siègent sur #!Bots!, tous en +B et tous déclarés, la page du panel dit « constaté — 5 robot(s) », aucun écart n'est signalé. ⚠ Ce qui reste ouvert n'appartient plus à ce point : l'affichage sur le site public n'est pas tranché (voir ci-dessous), et le compte claude du panel reste sans mot de passe utilisable. |
|---|
⚠ L'état d'avant, gardé pour mémoire — ce bloc était écrit en <details>/<summary> HTML jusqu'au 25/08/2026 ; le rendu du site échappe tout le HTML avant mise en forme, si bien que le visiteur lisait les balises en clair au lieu d'un repli.
| Pour fermer (au 15/08 19:30) | Quatre gestes, aucun fait. (1) #!Bots! inscrit dans ${t}_channels — sans quoi rien ne le balaie, et la page le dira « non suivi » ; (2) les quatre OverOp y entrent ; (3) ils portent +B — mesuré le 15/08, ils sont en +ix quand IriX porte déjà +B (+iowsxzB) ; (4) ils sont déclarés dans protection.conf, par le panel. ⚠ sudo ne couvre pas overop@ : je ne peux pas redémarrer un robot, il faut passer par leur console DCC. ⚠ Et un +B posé à la main est perdu au reconnect — pour qu'il tienne il doit s'écrire dans overop.tcl, sur l'événement de connexion. |
|---|
⚠ Ce qui reste à trancher, et qui appartient à SerVuS : la section est au panel. Le site public n'a aucune page « robots » — ses 16 routes sont fixées, et son propre code prévient qu'au-delà de onze entrées la barre de menu déborde de son conteneur (site_nav()). Une 17ᵉ page publique, une greffe sur une page existante, ou le panel seul : la question n'est pas tranchée.
15. ✅ La veille de Windows gèle WSL — TRANCHÉ le 14/08/2026 : assumé, et tenu par un banc
⚠ DÉCISION D'EXPLOITATION, PAS UNE CORRECTION. Le gel n'est pas corrigé et ne le sera pas : un passage horaire tombant pendant une veille reste perdu. Ce qui a changé, c'est qu'on sait exactement ce qu'il coûte — un retard, jamais une donnée — et que la discipline qui rend ce coût acceptable ne repose plus sur la vigilance. Voir « Ce qui a été tranché » en fin de point.
Le corps ci-dessous est gardé tel quel : c'est lui qui porte la mesure, et les deux fausses pistes qu'il a fallu écarter.
Trouvé le 14/08/2026, en cherchant pourquoi IriX-sql.log s'était tu à 10:00:00.
Le fait : la machine hôte passe en veille moderne (Modern Standby), et WSL gèle avec elle. Un bind time Eggdrop se déclenche quand le processus voit passer la minute :00 — gelé à cet instant précis, le déclenchement est perdu, et Eggdrop ne le rejoue jamais.
Mesuré le 14/08/2026 : les scans horaires de 11:00, 12:00 et 13:00 n'ont pas eu lieu. Ni trace, ni erreur, ni signal — le mécanisme est sain, il n'a simplement pas été appelé.
Veilles et réveils de l'hôte (journal Windows Kernel-Power, ID 506/507) :
10:49:59 entrée en veille
11:30:32 sortie, puis ré-entrée aussitôt
12:22:27 sortie, puis ré-entrée
13:02:21 sortie, puis ré-entrée
13:13:35 sortie
Et les PING du serveur reçus par IriX : 10:49 · 10:55 · 11:30 · 12:22 · 13:13, puis une par minute à nouveau. 140 minutes sur 201 sans une seule ligne au journal. Les deux séries se recouvrent exactement — c'est ce recouvrement qui fait la preuve, aucune des deux ne suffisait.
⚠ uptime ne compte pas le temps gelé. Il annonçait 1h14 quand le bot tournait depuis 3h40 d'horloge murale ; la différence — environ 2h26 — est le gel. dmesg porte le hv_utils: TimeSync de la resynchronisation au réveil. Ne jamais lire uptime comme une durée de service sous WSL.
⚠⚠ Le keepalive n'y peut rien, et il n'est pas en cause. Service-Overtchat - WSL keepalive tournait pendant tout l'épisode — tâche Running, lanceur VBS actif, dernier passage à 13:26. Il maintient la distro vivante quand aucune session n'y est attachée, ce qui est un autre défaut. Rien du côté WSL n'empêche Windows de dormir.
Ce que ça touche : tous les bind time du projet — ::soc::scan::hourly et ::soc::scan::daily (IriX-binds.tcl:220-221). Le scan horaire porte le contrôle des prêts, la purge du plafond botreg et le scan WHO ; le scan quotidien porte la rotation des journaux.
⚠ Deux fausses pistes écartées, et elles menaient loin. Le journal IriX-sql.log a été accusé en premier — il accepte l'écriture : essai direct en ajout concluant, droits et disque bons, aucun attribut immuable. Puis une fuite de descripteurs a été supposée — 8 descripteurs ouverts sur une limite de 1024. Le journal ne se taisait pas : il n'avait plus rien à écrire. ⚠ Un journal muet fait accuser le journal ; c'est son producteur qu'il faut mesurer.
✅ Ce qui ne casse PAS, et il faut le dire : un seuil comparé à l'horloge murale se rattrape tout seul. suspendre_non_servis teste if {$duree < $seuil_min} { continue } (irix-mandat.tcl:1472) — un <, pas un ==. Un passage manqué est donc repris au suivant, avec le bon verdict et un horodatage décalé. C'est ce qui sauve le constat du point 11.
✅ LE CROISEMENT QUE CE POINT RÉCLAMAIT EST FAIT — 14/08/2026. Il demandait de chercher les déclencheurs qui comptent des passages plutôt que du temps, seuls à perdre vraiment des données. Mesuré :
| Ce qu'on a cherché | Ce qu'on a trouvé |
|---|---|
bind time — les seuls déclenchements perdables | deux, ::soc::scan::hourly et ::soc::scan::daily (IriX-binds.tcl:220-221) |
utimer — décalés par un gel, jamais perdus | treize, dont celui qui porte ::soc::admin::scan |
| seuils posés sur un compteur de passages | aucun |
| comparaisons à l'horloge ou à la base | 120, réparties sur 14 fichiers |
Le projet compare du temps, pas des tours. Le risque que ce point redoutait ne s'est donc pas matérialisé — sauf en un endroit, et un seul : le scan quotidien, qui ne rejouait rien du tout parce qu'il ne partait jamais. C'est le point 16, ouvert et traité le même jour.
⚠ Le gel n'est pas corrigé, et un passage horaire tombant pendant une veille est toujours perdu. Mais il est désormais borné — on sait ce qu'il coûte (des passages, pas des données) et ce qu'il ne coûte pas. C'est ce bornage qui a rendu l'arbitrage possible.
Contre-épreuve du 14/08, qui vaut mieux qu'un raisonnement : passages horaires du jour à 10:00, 14:00 et 15:00, rien à 11, 12 ni 13 — machine gelée. Et entre 14:15 et 15:00, avec la machine éveillée, 46 minutes journalisées sur 46, gel cumulé figé à 2h23. Le mécanisme n'a jamais été en cause ; seule l'absence de la machine l'était.
✅ LA PISTE (b) N'EST PLUS UNE HYPOTHÈSE — elle a tourné le 14/08/2026 à 17:13:47. Le rattrapage du scan quotidien, écrit le jour même, s'est déclenché seul au premier démarrage qui a suivi : 33 secondes après le lancement du service, sans que personne le provoque. Le motif « un utimer qui se réarme + une garde sur le jour calendaire + une mémoire en base » fonctionne en réel, et non plus seulement au banc. Voir le point 16, clos.
⚠ Cela ne ferme pas ce point-ci, et il faut dire pourquoi. Le rattrapage rend un passage quotidien retrouvable ; il ne rend rien au passage horaire, qui reste perdu quand la veille tombe sur la minute :00. Le gel lui-même n'est pas corrigé — il est seulement contourné là où le contournement avait un sens.
Ce qui a été tranché — 14/08/2026, option (c)
SerVuS a délégué l'arbitrage : « fait tes propres choix ». Les trois pistes étaient (a) empêcher la veille de l'hôte, (b) rattraper au réveil comme le fait désormais le scan quotidien, (c) l'assumer.
C'est (c). Et ce n'est pas un renoncement : c'est ce que la mesure soutient.
| Piste | Pourquoi elle n'a pas été retenue |
|---|---|
| (a) empêcher la veille | Elle règle tout, mais au prix d'un réglage d'alimentation hors dépôt sur la machine personnelle de l'exploitant, qui ne dormirait plus jamais. Le remède coûte plus que le mal. |
| (b) rattraper l'horaire | Le motif est écrit et constaté pour le quotidien. L'appliquer à hourly demanderait de le rendre idempotent — et n'apporterait presque rien : le croisement du 14/08 montre que ses trois travaux comparent du temps, donc se reprennent seuls au passage suivant. |
Ce que (c) exige, en une phrase : tout seuil horaire se compare en < ou >= à l'horloge murale ou à la base, jamais à un compteur de passages. Le croisement montre la règle déjà respectée partout — zéro seuil sur un compteur de passages, 120 comparaisons à l'horloge.
⚠⚠ UNE DISCIPLINE QUI NE REPOSE QUE SUR LA VIGILANCE SE PÉRIME, et ce carnet en a la preuve à chaque page. C'est pourquoi (c) a été outillée le jour même plutôt que simplement décidée :
- la convention vit à côté du code, dans
IriX-binds.tcl: chaquebind timeporte une ligne# RATTRAPAGE:qui dit soit la procédure qui le rattrape, soit que son absence est ASSUMÉE et pourquoi. Une déclaration posée ailleurs se serait périmée sans qu'on la voie ; - le banc
IriX/tests/test-bindtime.tclla tient : il dérive lesbind timedes sources — il n'en énumère aucun — et rougit sur un bind ajouté sans déclaration. 8 constats.
⚠ Ce que ce banc NE fait PAS, et il faut le savoir : il ne lit pas ce que fait la procédure liée. Une ligne « ASSUMÉ » posée sur un bind qui compte des passages le satisfait pleinement. Il constate une déclaration, pas sa véracité — c'est un point de passage obligé, pas une preuve.
⚠ Une mutation a été ratée au premier tour, et elle a corrigé le banc. Le constat « le rattrapage est appelé depuis admin::scan » se contentait de trouver le nom dans le corps : retirer l'appel en laissant la garde if {[llength [info commands …]]} le laissait vert. Or cette garde ne déclenche rien — elle vérifie que la procédure existe. Le banc gardait la porte en regardant la serrure. Corrigé, puis les cinq mutations attrapées.
⚠ Une autre a été attrapée avant elle, et du même genre : l'extraction du corps d'admin::scan par expression régulière rendait 266 146 caractères, soit la moitié du fichier. Le constat passait au vert en cherchant dans presque tout IriX-sql.tcl — vrai pour une mauvaise raison, ce qui est pire que faux. Remplacée par le comptage d'accolades de test-mandat.tcl, repris et non réinventé.
| Ce qui rouvrirait ce point | Un seuil écrit en comptant des passages plutôt que du temps — le banc ne l'attrapera pas, seule la relecture de la ligne # RATTRAPAGE: au moment où on l'écrit le fera. Ou un changement d'usage de la machine : ce point tient parce que le gel coûte un retard, ce qui est acceptable pour des purges et des contrôles de prêts. Il ne le serait plus pour un geste dont l'heure fait partie du service — une annonce, une coupure programmée, un envoi. |
|---|
16. ✅✅ Le scan quotidien — CLOS le 14/08/2026, il s'est rattrapé tout seul
⚠ CONSTATÉ FERMÉ le 14/08/2026 à 17:13:47, quatre heures après l'avoir écrit — voir le constat en fin de point. Le service a redémarré de lui-même, admin::scan a appelé daily_rattrapage, et le scan quotidien a tourné pour la première fois depuis au moins le 27/07. Le corps du point est gardé tel quel : c'est lui qui dit pourquoi le défaut existait, et ce qu'il a fallu écarter.
Trouvé le 14/08/2026, en faisant le croisement que la ligne « Pour fermer » du point 15 réclamait. Le point 15 disait que le vrai risque était ailleurs ; il était là.
Le fait : ::soc::scan::daily est lié par bind time - "00 00 * * *" (IriX-binds.tcl:221) et s'ouvre sur if {$hour ne "00" || $min ne "00"} { return }. Or le service est arrêté chaque soir vers 21:00 et redémarré vers 09:00. Minuit tombe dans le trou, tous les jours.
2026-08-13T21:00:02 Stopping irix.service
2026-08-14T09:01:43 Started irix.service
Mesuré le 14/08/2026 — quatre comptages indépendants, tous à zéro :
- 0 ligne journalisée à minuit dans les 14 archives du 31/07 au 14/08 ;
- 0 occurrence de « scan quotidien » dans
journalctl, qui remonte au 27/07 ; - 0 occurrence de « deauth quotidien », que seul
dailyécrit ; - heures présentes dans les archives : 08 à 23, jamais 00 à 07.
Ce qui ne s'exécute donc jamais tout seul, chaque ligne vérifiée par la liste de ses appelants :
| Travail | Autre chemin | Ce que ça coûte |
|---|---|---|
::soc::hist::purger | aucun | l'historique n'est jamais purgé, alors que 30 jours sont annoncés |
::soc::nickexpire::balayage | !nickexpire, à la main | les pseudonymes n'expirent jamais d'eux-mêmes |
::soc::ia::daily_purge | !iapurge, à la main | le corpus de l'IA n'est jamais purgé |
is_oper, away | aucun | les deux remises à zéro vivent dans le corps de daily |
| — | retirée le 14/08/2026, pas rattrapée — voir ci-dessous |
⚠ Une supposition écartée par la mesure : ia::daily_purge semblait sauvé par un second appel en IriX-sql-IA.tcl:1130. Cette ligne est dans ::soc::ia::pub_purge — la commande manuelle. Le fichier porte bien un utimer (l. 1174), mais il lance promote::evaluate, pas la purge. Compter les appelants ne suffit pas : il faut regarder dans quelle procédure chacun vit.
⚠⚠ Le commentaire de daily avait prévu le défaut sans voir par où il viendrait : « une procédure écrite mais jamais appelée est le défaut le plus discret de ce projet ... elle est ici parce que c'est le seul balayage quotidien ». hist::purger est appelée dans le code. C'est son déclencheur qui ne se déclenche pas. Un appel présent dans la source ne prouve pas un appel exécuté — et aucune relecture ne rattrape cela, seule une trace le fait.
✅ LE REMÈDE EXISTE DÉJÀ DANS CE DÉPÔT, ET IL TOURNE — DEUX FOIS.
overtchat-backup.timerporteOnCalendar=*-*-* 04:00:00etPersistent=true. Prévu à 04:00 pendant que la machine dormait, il s'est exécuté à 09:03:11 au réveil le 14/08 — sauvegarde et rotation comprises. Son propre commentaire le dit : «Persistent=trueEST L'ESSENTIEL ICI, et pas un détail de confort ».irix-logrot.tclfait la même chose en Tcl :::soc::logrot::_due {policy last}compare la politique à la dernière exécution, jamais à l'heure courante. Constaté le 14/08 — la ligne « rotation planifiée (daily) » est écrite à 09:02:16, pas à minuit.
⚠ CE QUE LE SOMMEIL NE CASSE PAS — et il fallait le mesurer pour le savoir.
Un trou nocturne fait naturellement soupçonner tout ce qui est planifié. Mesuré le 14/08/2026 : le reste va bien, et chacun pour une raison qu'il faut garder ici, sans quoi on refera l'enquête.
| Ce qui tombe dans le trou | Verdict |
|---|---|
| Les dix minuteurs systemd de la machine | tous en Persistent=true — rattrapés au réveil |
certbot.timer (*-*-* 00,12:00:00) | rattrapé ; dernier passage le 13/08 à 20:02, et le certificat court jusqu'au 19/10/2026 |
logrotate | tourne — exécuté le 14/08 à 10:28:14, soit au réveil et non à l'heure prévue |
/etc/cron.daily (6h25, anacron absent) | ne tourne jamais — et cela ne coûte rien |
⚠ Le cas de cron.daily a besoin de sa raison, sinon il sera rouvert : sur ses six tâches, quatre sont doublées par un minuteur systemd qui rattrape (apt-compat, dpkg, logrotate, man-db). Des deux autres, apache2 ne fait rien même lorsqu'il tourne — il sort sur [ "$HTCACHECLEAN_MODE" = "cron" ] || exit 0, et le mode est daemon — et aptitude se borne à archiver pkgstates dans /var/backups. Aucune dérive : /var/log à 862 Mo, disque à 2 %.
Le seul dégât du sommeil nocturne est donc ce point-ci, et c'est précisément ce qui le rend traitable : un endroit à corriger, pas une famille.
✅ LA MOITIÉ TECHNIQUE EST ÉCRITE ET DÉPLOYÉE — le 14/08/2026, commit 214cfaf. Le motif de logrot a été transposé, pas réinventé :
| Pièce | Où, et pourquoi elle tient |
|---|---|
| un timer qui se réarme | ::soc::admin::scan, porté par utimer — décalé par un gel, jamais perdu, contrairement au bind time |
| une garde sur le jour calendaire | ::soc::scan::daily_due — trois jours manqués ne donnent qu'une reprise, ce qui est le bon compte pour des purges par ancienneté |
| une mémoire en base | clé daily_last_run dans soc_settings, table qui existait déjà et ne servait à rien |
Le bind time de minuit est conservé pour le cas nominal ; la garde rend les deux chemins inoffensifs l'un pour l'autre, le second passage d'une même journée ne faisant rien.
⚠ Arbitrage tranché par SerVuS — option (a) : les deux remises à zéro globales entrent dans le rattrapage. Elles supposent que personne n'est connecté, ce qui est vrai à minuit et au réveil de la machine, le bot venant alors de démarrer. L'option inverse — les laisser au seul bind time — les aurait conservées dans leur défaut d'aujourd'hui : ne jamais tourner.
Éprouvé : daily_due juste sur cinq cas (mémoire vide, hier, il y a trois jours, aujourd'hui, mémoire illisible) ; mutation attrapée — remplacer le ne de la comparaison par eq fait échouer deux cas ; les quinze bancs Tcl qui lisent IriX-sql.tcl verts avant comme après, sur un arbre complet. ⚠ Un rouge de test-doublons a d'abord été imputé à tort : il venait d'un bac à sable où manquaient les .sql que ce banc compare — cible inexistante, « attendu » vide.
⚠⚠ DÉPLOYÉ SANS RECHARGEMENT, ET C'ÉTAIT DÉLIBÉRÉ. À 14:43 le fichier était en place, mais IriX tournait encore l'ancien code en mémoire : PID inchangé, zéro trace de rechargement, zéro « scan quotidien » au journal. Recharger sur-le-champ aurait déclenché le scan à une heure choisie au hasard — envois de courriels d'expiration compris.
✅ Et l'attente a payé deux fois. Le rattrapage a pris effet au premier démarrage venu, à 17:13:14, ce qui en fait un constat et non une démonstration (voir en fin de point). Et la précaution s'est révélée sans objet : aucun courriel n'est parti, faute de pseudonyme échu. ⚠ Ne pas en tirer que la précaution était inutile — elle était juste, et son coût était nul : trois heures d'attente pour une machine qui redémarre seule.
⚠⚠ ET LE RATTRAPAGE A RÉVÉLÉ UN DÉFAUT DANS CE QU'IL ALLAIT FAIRE PARTIR. La déauthentification quotidienne a été retirée le 14/08/2026 (5938c53), et non rattrapée.
Elle faisait onchan $nick sur [channels] seulement, et déauthentifiait qui ne s'y trouvait pas — exactement le raisonnement périmé corrigé dans ::soc::auth::scan le 03/08/2026 : un utilisateur identifié qui ne partage aucun salon avec IriX, cas normal de quelqu'un qui discute sur ses propres salons, est parfaitement connecté et se voyait déauthentifié. Le défaut avait survécu ici parce que ce scan ne partait pas — rien ne pouvait le manifester —, et il aurait frappé au premier rattrapage, sur de vrais utilisateurs.
Rien n'est perdu : ::soc::auth::scan fait ce travail toutes les 60 secondes, avec une garde de démarrage (« reset boot ») que le geste retiré n'avait pas, et en consultant le registre du lien en plus des salons du bot. ⚠ Ne pas le réintroduire « pour plus de sûreté » : une seconde autorité sur l'identification est une régression, pas une ceinture — celui des deux verdicts qui divergerait serait le nôtre, puisqu'il en sait moins.
⚠ La leçon dépasse ce point. Rendre joignable un code qui ne tournait pas, c'est réveiller tout ce qu'il contient — y compris ce que personne n'a relu depuis qu'il dort. C'est pour cela que les six gestes ont été repris un par un, et non pas seulement le mécanisme qui les appelle.
✅✅ LE CONSTAT A EU LIEU LE 14/08/2026 À 17:13:47 — CE POINT EST CLOS.
Il n'a pas été provoqué : la machine a redémarré d'elle-même à 17:13:12 (boot -1 clos à 17:12:37, boot 0 ouvert à 17:13:12 — un vrai redémarrage, pas une sortie de veille), le service est parti à 17:13:14, et 33 secondes plus tard le scan tournait. C'était le premier démarrage postérieur au déploiement de 14:43:34 : celui de 09:46 lui était antérieur, le code n'existait pas encore.
2026-08-14T17:13:47 [INFO] scan quotidien démarré
2026-08-14T17:13:47 [INFO] scan quotidien terminé
Les quatre exigences, une par une :
| Ce qui était demandé | Ce qui a été mesuré |
|---|---|
scan quotidien démarré puis terminé dans IriX-sql.log | ✅ les deux, à 17:13:47 — et aucune occurrence avant dans un journal qui remonte à 09:02:21, donc couvrant le démarrage de 09:46 |
la clé daily_last_run à la date du jour dans soc_settings | ✅ daily_last_run = 2026-08-14 dans IriX_soc_settings |
| le second passage du jour ne doit rien faire | ✅ le cycle est de 30 s (rehash_interval, IriX-src.conf:75) : à 17:22:25, ~17 cycles avaient passé et les trois traces valaient toujours 1 — un démarré, un terminé, une purge quotidienne |
| ce que la première passe a réellement purgé | ✅ presque rien, et c'est mesuré — voir ci-dessous |
⚠ LA TRACE DE LA PURGE IA N'EST PAS DANS IriX-sql.log. Elle est écrite par putlog, donc dans IriX/runtime/IriX.log :
[14/08 17:13:47] SOC IA: purge quotidienne effectuee
La ligne « Pour fermer » de ce point disait de chercher « dans IriX-sql.log seulement ». C'est faux, et un grep obéissant y aurait conclu que la purge IA n'avait pas tourné. Les deux journaux servent, et ils ne portent pas les mêmes lignes.
✅ Ce que la passe a fait, et ce qu'elle n'avait pas à faire. Ce point redoutait qu'une purge muette depuis le 27/07 « ait beaucoup à faire d'un coup ». Mesuré : elle n'avait presque rien à faire, chaque table étant en deçà de sa rétention.
| Table | Rétention | État après la passe |
|---|---|---|
chat_hist | 30 j | 54 lignes, la plus ancienne du 09/08 — rien d'échu |
ia_inbox / ia_outbox | 7 j | 9 et 9, la plus ancienne du 09/08 15:29 — rien d'échu |
ia_log | 90 j | 9 lignes — rien d'échu |
ia_queue | 30 j | vide |
ia_tokens | total_count < 2 | 0, l'état voulu du point 1 |
Contrôle croisé : zéro ligne au-delà de son seuil dans les trois tables datées.
⚠ hist::purger n'a rien retiré, et son SILENCE le prouve — elle ne journalise que si avant ne apres (IriX-sql.tcl:12054). Confirmé par les identifiants : chat_hist va de 1 à 54 sans trou, aucune ligne n'a jamais été supprimée. ⚠ En revanche ia_inbox commence à l'id 10 — des lignes plus anciennes ont bien disparu, mais rien ne dit que c'est cette passe-ci : le corpus a été purgé à la main deux fois selon le point 1. Un trou d'identifiants prouve une suppression, jamais sa date.
✅ AUCUN COURRIEL D'EXPIRATION N'EST PARTI, et c'était le risque assumé au déploiement. nickexpire::balayage a tourné sans rien envoyer : la dernière ligne d'IriX_mail_log date du 05/08, et son origine expiration-pseudo remonte au 05/08 également. Aucun pseudonyme n'était échu — le report du rechargement s'est révélé prudent pour rien, ce qui est le bon sens du terme.
| Ce qui reste | Rien pour ce point. ⚠ Mais deux choses en héritent. (1) Le bind time de minuit demeure, inutile et inoffensif — la garde le neutralise, et il servira le jour où la machine ne dormira plus. (2) La prochaine passe est le vrai second constat : celle-ci n'avait rien d'échu à retirer, donc elle n'a pas éprouvé les DELETE sur des données réelles. Le jour où chat_hist dépassera 30 jours — au plus tôt le 08/09/2026, la plus ancienne ligne datant du 09/08 —, vérifier que hist: purge — N ligne(s) retirée(s) apparaît dans IriX-sql.log. Tant qu'elle ne l'a pas fait, la purge est appelée sans être éprouvée en charge. |
|---|
1. ✅✅ L'IA peut s'amorcer seule — CLOS le 09/08/2026, RECONSTATÉ le 14/08/2026
Reconstaté le 14/08/2026, cinq jours après la fermeture : les trois gardes d'amorce_ok sont en place, test-ia-amorce.tcl passe ses 15 vérifications, antimot=oui en warn sur quatre salons, mode learn sur les quatre, les 17 mots du filtre présents et ia_tokens à 0 — l'état voulu. Rien n'a régressé ; il n'attend que du trafic réel, ce qui n'est pas un défaut.
Où : IriX-sql-IA.tcl, ::soc::ia::score (l. 417) et la branche d'apprentissage (l. 561-597).
Sans jeton, score rend {0.0 0}. Le score passe sous le seuil, et la branche else n'entraîne que si $sig >= $min_tokens — or $sig vaut 0. Le modèle bayésien ne peut donc jamais s'amorcer par lui-même.
Sa seule amorce : ::soc::ia::train_from_sanction, appelée par check_antimot_bridge (IriX-sql.tcl:11279) quand le filtre de mots sanctionne. Chaîne vérifiée complète le 07/08/2026. Elle part même en mode warn — sanctionner et entraîner sont deux gestes distincts.
Pourquoi rien n'était appris : antimot n'était actif que sur #Csadmin, un salon d'administration où personne ne jure. Activé sur #accueil et #membres le 07/08.
Mesuré le 09/08/2026 — ia_tokens vaut toujours 0, et le blocage a changé de nature :
- les 4 seuls messages jamais captés par l'IA sont sur
#!Overtchat!(28/07 et 05/08), tous en score 0 et décisionskip— le cercle vicieux, tel quel ; - or
antimoty valaitnon. Le seul salon qui avait vu du monde n'avait pas le déclencheur. Armé enwarnle 09/08 ; - sur
#accueilet#membres, armés le 07/08 : aucun message capté en deux jours. Le journal du bot ne porte que ses propres gestes — topics et modes. Ce n'est pas un défaut de capture : personne n'a parlé.
Le blocage n'est donc plus un réglage, c'est l'absence de trafic. Les quatre salons publics portent maintenant antimot=oui en warn, et les 17 mots du filtre sont de portée globale.
✅ Le cercle vicieux est ROMPU dans le code, le 09/08/2026. analyse sortait par skip sans rien apprendre dès que $sig < $min_tokens — et avec zéro jeton, c'était toujours vrai. La sortie était sans retour.
::soc::ia::amorce_ok ouvre la seule porte défendable : apprendre good sur un message que le filtre déterministe vient de laisser passer. C'est la symétrie exacte de train_from_sanction — le même juge, l'autre verdict. La présomption tient parce que check_antimot_bridge filtre en deux couches et fait return dès qu'un mot interdit est trouvé : tout message qui atteint l'IA a déjà été jugé propre.
Trois conditions, chacune contre un défaut distinct : antimot doit être armé sur le salon (sinon personne n'a jugé, et présumer bon ce que nul n'a regardé revient à inventer un corpus), le mode doit être learn (en review/auto, un modèle ignorant apprendrait en masse pendant qu'il sanctionne), et $sig doit rester sous le seuil. ⚠ Elle s'éteint toute seule dès que le corpus atteint min_tokens : rien à débrancher, donc rien à oublier de débrancher.
Éprouvée par IriX/tests/test-ia-amorce.tcl, 15 constats et 4 mutations — dont la plus importante : retirer la garde antimot fait rougir le banc, ce qui prouve qu'il tient bien la porte par laquelle on inventerait un corpus.
✅ CONSTATÉ EN PRODUCTION le 09/08/2026 : la chaîne entière fonctionne. Sept messages ordinaires envoyés sur #accueil par un client IRC, avec l'accord de SerVuS. ia_inbox 4 → 11, ia_outbox 4 → 11, ia_log 0 → 7, et ia_tokens 0 → 68 au premier passage. Le cercle vicieux est rompu pour de bon : le modèle pose ses jetons dès le premier message ordinaire.
⚠ ET LE PREMIER TRAFIC RÉEL A MONTRÉ UN AUTRE DÉFAUT, plus grave — voir la 4.16.5 : la tokenisation coupait tous les mots accentués en deux. « découvre » donnait couvre, « réseau » donnait seau, « agréable » donnait agr + able. Un modèle de modération francophone bâti sur des moitiés de mots ne peut pas fonctionner. Corrigé, éprouvé, et reconstaté : les jetons sortent désormais entiers — agreable, reseau, redecouvre, francais, traite.
⚠ Le corpus a été PURGÉ après la preuve, deux fois : 68 jetons fragmentés, puis 42 jetons propres. ia_tokens est à 0 et doit y rester jusqu'à de vrais visiteurs. Le modèle ne doit pas apprendre son vocabulaire d'une machine — c'est la règle du carnet, et la preuve ne la lève pas. Les traces restent (ia_inbox, ia_outbox, ia_log) : elles n'entrent dans aucun score.
| Pour fermer | Rien de technique — le point est clos côté code. Il ne reste qu'à laisser de vrais visiteurs parler. ⚠ Ne PAS lire un ia_tokens à 0 comme une panne : c'est l'état voulu tant que personne n'est venu. |
|---|
⚠ NE PAS injecter de jetons d'essai pour « prouver » que ça marche : du vocabulaire inventé fausserait durablement tous les scores futurs.
⚠⚠ ET TOUT ESSAI SUR UN SALON ARMÉ EN APPREND, MÊME SANS LE VOULOIR. Le 10/08/2026, quatre commandes !newban / !delban lancées sur #accueil pour éprouver la chaîne des bans ont fait passer ia_tokens de 0 à 16 — essai, bans, chaine, invalid, inewban, idelban. Personne n'avait injecté quoi que ce soit : amorce_ok apprend good sur tout message que le filtre laisse passer, et une commande en est un.
Le corpus a été purgé, et les traces conservées — ia_inbox, ia_outbox et ia_log n'entrent dans aucun score.
La règle qui en découle : un essai sur un salon où antimot est armé se fait sur #test, ou se termine par une purge. Les quatre salons publics sont armés depuis le 09/08 — il n'y a plus de salon « neutre » parmi eux.
2. ✅✅ Étendre le protocole en étoile — CLOS le 15/08/2026, une information a PASSÉ
⚠ CONSTATÉ FERMÉ le 15/08/2026 à 21:38:37, et par la seule preuve que ce point acceptait encore : une information a traversé le lien de botnet, les deux extrémités tracées.
21:38:37 IriX .tcl ::soc::botnet::ping OverOp-T
21:38:37 OverOp-T triggering bind ::overop::on_ping
21:38:37 OverOp-T OVEROP: écho de IriX — répondu
21:38:37 IriX triggering bind ::soc::botnet::on_pong
21:38:37 journal observed · OverOp-T · « écho revenu en 0s — le lien porte »
Le lien lui-même est monté depuis 21:29:41 — challenge, réponse, « Relié à OverOp-T », socket ESTAB entre les PID 39327 et 42161 — et le module l'a constaté seul au cycle suivant : « observation — 1 lien(s) monté(s) », state = linked.
⚠⚠ Ce point a failli se fermer sur une preuve creuse, DEUX FOIS
Premier faux départ — le lien monté sans verbes. Le module faisait 802 lignes et rien ne pouvait y circuler : zéro bind bot, zéro putbot. Ce point annonçait trois réglages manquants ; il en manquait un quatrième, plus lourd. Sans lui, un lien monté aurait eu l'air parfaitement sain dans la page — et n'aurait servi à rien. C'est ce constat, fait le 15/08 en mesurant avant d'écrire, qui a durci l'exigence de fermeture : « constater qu'une information PASSE ».
Second faux départ — le transport écrit d'un seul côté. Les verbes posés, le lien monté, l'écho émis… et rien. overop.tcl n'avait aucun bind bot : IriX émettait, le lien portait la ligne, et le robot n'en savait rien. Pas de réponse, pas de trace, pas d'erreur — un silence. Le motif « à la jointure » du projet, refait le jour même où il était corrigé pour irix-edition.tcl.
⚠ Et le banc était vert sur ce transport à moitié écrit, parce qu'il ne lisait que les binds d'IriX. Un transport a DEUX extrémités ; un banc qui n'en regarde qu'une ne garde rien. Le §4bis de test-botnet-echo.tcl croise désormais les deux sources dans les deux sens.
Ce qui a été écrit, et ce qui a été refusé
⚠ Un écho, et rien d'autre. Faire transiter ici les ordres de prêt aurait dupliqué le protocole OverBot, qui les porte déjà par IRC avec UID vérifiable, plafond et corrélation — « lequel a raison ? » à chaque divergence. Ce que le botnet apporte et que l'IRC ne peut pas donner, c'est d'être hors du serveur : si le lien S2S tombe, les bots peuvent encore se parler.
⚠ Un pong ne répond jamais, des deux côtés. ⚠ L'horodatage est celui de l'émetteur, recopié tel quel : une seule horloge mesure. ⚠ Aucune migration : l'écho emploie les valeurs d'ENUM existantes plutôt qu'un ALTER qui pourrait n'être jamais appliqué.
IriX/tests/test-botnet-echo.tcl — 12 mutations, 12 attrapées, dont la reproduction des deux faux départs.
⚠ Les trois réglages, et ce qu'ils ont vraiment coûté
botnet-nick a été posé par script (ce n'est pas un secret) ; .+bot, .chaddr et .chpass ont été faits par l'exploitant, comme la règle l'exige.
⚠⚠ ET J'AI DONNÉ UNE SYNTAXE FAUSSE, qui a coûté deux essais. J'ai écrit .+bot OverOp-T 127.0.0.1:3344 — avec des deux-points. Eggdrop 1.10 attend l'adresse et le port séparés par une espace, et l'aide du bot le dit : chaddr <bot> <address> [[+]bot port[/[+]user port]]. Le userfile a donc enregistré --BOTADDR 127.0.0.1;3333:3333/3333 et le bot a cherché une machine nommée 127.0.0.1;3333. La ligne d'échec le disait en clair — Liaison avec OverOp-T at [127.0.0.1:3344]:3333 — et c'est elle qui a tranché, pas une relecture du code. Lire l'adresse que le bot annonce, jamais celle qu'on croit avoir posée.
| Pour fermer | ✅ RIEN — clos le 15/08/2026. ⚠ Ce qui reste et n'appartient pas à ce point : OverOp-H est déclaré comme pair mais non lié — son userfile ne porte aucun compte utilisable (ClaudeEssai est sans mot de passe, et overop.conf ne source aucun script standard, donc /msg … pass n'existe pas). Lier les trois autres robots demandera d'abord de leur créer un accès. ⚠ Et le lien monté ne survit pas à un redémarrage : .link est un acte, pas un réglage. |
|---|
2. L'historique de ce point — bascule de charge et objections d'origine
⚠⚠ CE POINT SE CONTREDISAIT LUI-MÊME, et pas par péremption — les deux phrases cohabitaient depuis le 12/08. Son titre annonçait « bascule de charge à écrire » quand sa propre ligne « Pour fermer », vingt lignes plus bas, la déclare écrite, éprouvée (32 constats, 9 mutations) et constatée en réel le 12/08 à 14:00:00. Trouvé le 13/08/2026 en mesurant le carnet avant de travailler dessus, et non par une relecture — c'est le seul geste qui attrape ce genre d'erreur.
📌 Ce que la ligne « Pour fermer » ferme, et ce qu'elle NE ferme PAS. Elle ne parle que de la bascule de charge. Le botnet, lui, reste ouvert : les trois réglages qui vivent hors de la base n'ont pas été posés, et rien ne montera sans eux — voir l'avertissement plus bas.
Le canal actuel a été mesuré le 09/08/2026, comme le demandait ce carnet. Il porte trois verbes : REOP montant, JOIN et PART descendants, plus les accusés.
Ce qu'il ne sait pas faire : rien de robot à robot, aucune commande d'administration à distance, aucune partyline commune, aucune bascule de charge.
⚠⚠ LE BOTNET A ÉTÉ ÉCARTÉ LE 09/08/2026, PUIS REPRIS LE 12/08 — les deux par SerVuS. Le carnet garde les deux décisions, et surtout les trois objections d'origine : elles n'ont pas été réfutées, elles ont été traitées une par une, et deux d'entre elles restent vraies.
| Objection du 09/08 | Ce qu'elle devient le 12/08 |
|---|---|
Le partage de userfile créerait deux sources pour une même vérité — l'autorité est en base, et le userfile d'OverOp-H ne porte qu'un compte d'essai. | ✅ Contournée, pas réfutée. share-users n'est posé nulle part et irix-botnet.tcl ne le pose pas : le lien sert à se parler et à s'observer, jamais à répliquer des comptes. L'autorité reste en base. |
Un lien de botnet s'authentifie par un mot de passe partagé, là où le protocole OverBot part d'un UID vérifiable, avec plafond et corrélation. | ⚠ Reste vraie, et assumée. Le module n'y peut rien : c'est Eggdrop qui authentifie ainsi. Il refuse seulement d'aggraver — aucun secret en base, pas même une colonne pour l'accueillir, et le banc le vérifie. |
| Aucune charge à répartir : les robots d'alors servaient 0 salon sur 60 places. | ⚠ Reste vraie. Mesuré le 12/08 : un seul salon servi sur 80 places, trois robots sur quatre à zéro. Mais un botnet ne sert pas qu'à répartir — il donne une partyline commune et l'administration à distance, que le canal actuel ne sait pas faire. |
Ce qui est livré le 12/08/2026 : irix-botnet.tcl (deux tables, une boucle d'observation à 60 s, l'exécution des actions du panel), test-botnet.tcl (50 constats, 8 mutations toutes attrapées), et la page botnet du panel.
⚠ Ce qui change vraiment, et c'est l'essentiel : jusqu'ici la page « Robots Eggdrop » AFFIRMAIT « aucun lien de botnet n'est monté » — en dur, dans le HTML, sans rien mesurer. Elle avait raison, et elle aurait eu tort le jour où un lien serait monté, sans que personne ne le sache. L'état est désormais constaté, et un état périmé y redevient inconnu plutôt que de mentir.
⚠ CE POINT N'EST PAS FERMÉ, et rien ne montera en l'état. Trois choses vivent hors de la base et n'ont PAS été posées : botnet-nick dans les cinq configurations, .+bot/.chaddr sur chaque console, et .chpass — le secret partagé, qu'aucun automatisme ne posera. Déclarer un pair dans la page sans les trois donne un lien qui échoue, et l'échec s'affiche dans l'activité. Ce point ne se fermera qu'après un lien CONSTATÉ monté entre deux bots, pas après le déploiement du code.
✅ LE TRANSPORT EXISTE DEPUIS LE 15/08/2026 — 4.17.2
Deux verbes circulent désormais sur le lien : SOC-PING et SOC-PONG, liés dans IriX-binds.tcl, émis par ::soc::botnet::ping via ::putbot.
⚠ Un écho, et rien d'autre — c'est délibéré. Faire transiter ici les ordres de prêt dupliquerait le protocole OverBot, qui les porte déjà par IRC avec UID vérifiable, plafond et corrélation. Ce que le botnet apporte et que l'IRC ne peut pas donner, c'est d'être hors du serveur : si le lien S2S tombe, les bots peuvent encore se parler. L'écho mesure ce chemin-là.
IriX/tests/test-botnet-echo.tcl : 26 constats, 7 mutations, 7 attrapées — dont « faire répondre on_pong » (la boucle infinie) et « faire passer un BOTCMD par le botnet ».
⚠ botnet-nick est posé dans les cinq configurations — ce n'est pas un secret. Les deux autres réglages manquent toujours : .+bot/.chaddr et surtout .chpass, le secret partagé, que ce projet ne pose jamais par script.
⚠⚠ CE BANC GARDE LA FORME DU TRANSPORT, PAS SON SERVICE, et il l'écrit à chaque passage : aucune ligne n'a circulé. Le point reste donc ouvert — il ne se fermera qu'après avoir vu une information PASSER, comme la section ci-dessous l'exige depuis ce matin.
⚠⚠ CE QUI MANQUAIT — mesuré le 15/08/2026, corrigé le jour même
Ce point annonçait qu'il manquait trois réglages. Il en manque un quatrième, et il est plus lourd : le transport n'a aucun verbe. Dérivé des sources, jamais lu dans un document :
| Ce qui ferait circuler une information entre Eggdrop | Compte |
|---|---|
bind bot — recevoir une commande d'un autre Eggdrop | 0 |
putbot / putallbots — en émettre une | 0 |
bind chon · chof · bcst · link · disc | 0 chacun |
botnet-nick dans les cinq configurations | 0 |
| bots déclarés dans les cinq userfiles | 0 |
irix-botnet.tcl fait 802 lignes et 19 procédures — elles déclarent les pairs, observent leur état, journalisent les transitions et exécutent les actions du panel. Aucune ne porte de verbe. Le module monte et surveille un tuyau dans lequel rien ne peut passer.
⚠ Ce n'est pas un défaut du module : il fait exactement ce qu'il annonce, et son banc le prouve. C'est le point 2 qui promettait plus que le code ne livre — « la partyline commune et l'administration à distance » supposent un transport, et le transport n'existe pas.
⚠ Ce que ça change pour la fermeture de ce point : « un lien CONSTATÉ monté » ne suffira pas comme preuve. Un lien monté sans verbe est un lien qui ne sert à rien, et il aurait l'air sain dans la page. Il faut constater qu'une information PASSE, pas qu'un socket est ouvert.
✅ En revanche les robots communiquent DÉJÀ, et par un autre chemin — qui marche. Il ne faut pas le confondre avec le botnet, et ce carnet le dit ailleurs sans le rappeler ici :
| Côté | Comment |
|---|---|
| OverOp (client Eggdrop) | bind msg - REOP, bind msg - BOTCMD, bind notc - "BOTRES*" (overop.tcl:759-766) ; émission par putserv "PRIVMSG $OVERBOT :BOTREQ …" |
| IriX (service ULine, S2S) | aucun bind, et c'est normal — les lignes arrivent brutes sur la socket et sont traitées par le dispatcher (IriX-sql.tcl:3522 pour BOTREQ, :3559 pour BOTSTAT) |
⚠⚠ Un bind msg sur IriX NE VERRAIT RIEN, et s'y tromper coûterait une journée : IriX n'est pas un client Eggdrop connecté au serveur, c'est un lien de service. Ses deux bind pubm servent au pont antimot/IA, pas au protocole des robots.
📌 Et propager l'AUTHENTIFICATION par le botnet serait une régression, pas un raccourci. La question s'est posée le 15/08 : « auth validé sur un Eggdrop → tous les autres ». Mesuré — overop.tcl:12 le dit lui-même, « IL NE TOUCHE JAMAIS MySQL » —, un OverOp n'a aucune commande utilisateur et aucune notion d'auth : il exécute des ordres, il ne juge rien. L'auth vit en base (${t}_users.auth), écrite par IriX seul, relue par require_grade à chaque décision. Il n'y a donc rien à refaire un par un aujourd'hui.
Le jour où les robots prêtés auront leurs propres commandes, le besoin naîtra — et la réponse n'est toujours pas le botnet : copier l'auth dans quatre robots, c'est quatre révocations à réussir là où il n'y en a qu'une. La désauthentification est déjà différée de 4 s exprès et auth::scan repasse toutes les 60 s. Le canal BOTREQ/BOTRES existe et garde une seule autorité — c'est là qu'un verbe « cette personne est-elle authentifiée ? » doit aller. Voir la règle déjà écrite au point 16 : « une seconde autorité sur l'identification est une régression, pas une ceinture. »
⚠ Le parc, remesuré le 14/08/2026 : quatre robots pour 80 places — OverOp-H, -Q, -S, -T, vingt places chacun, tous online. OverOp-T s'est ajouté le 11/08, posé par l'installation automatique, d'où son origin = auto. ${t}_botreg ne porte toujours aucun enregistrement.
⚠⚠ LA CHARGE ANNONCÉE ICI ÉTAIT PÉRIMÉE DE DEUX JOURS. Ce point disait « un seul salon » et « #membres est fini ». Mesuré le 14/08 : deux mandats actifs.
| Salon | Robot | État | Accordé par |
|---|---|---|---|
#accueil | OverOp-H | actif | ClaudeOps |
#membres | OverOp-S | actif | essai-suspension-13-08 |
Le second est le prêt d'essai du point 11, posé le 13/08 pour éprouver la suspension. ⚠ Il se périme seul le 16/08 : la charge repassera à un salon sans que personne n'y touche, et cette ligne redeviendra fausse dans l'autre sens. C'est précisément pourquoi le paragraphe suivant dit de mesurer plutôt que de lire — un chiffre de charge ne survit pas à trois jours dans ce projet.
✅ DEUX CHOSES QUE CE POINT NE DISAIT PAS, constatées le 14/08/2026 :
- la boucle d'observation tourne vraiment.
${t}_botnet.seen_atétait à15:55:41à l'instant de la mesure — le cycle de 60 s vit, sans qu'aucun lien ne soit monté ; - un pair est déjà déclaré :
OverOp-Hsur127.0.0.1:3341, ajouté le 12/08 par « essai-12/08 », en étatunlinked. Il ne monte pas, faute des trois réglages ci-dessous — ce qui est exactement ce que le point annonce.
⚠ ${t}_botnet_log ne porte que 4 lignes, toutes du 12/08, et ce n'est PAS une panne : le module ne journalise que les changements d'état, jamais chaque observation. Un silence y signifie « rien n'a bougé », pas « rien ne tourne ». Confondre les deux ferait rouvrir un point sain — c'est le même piège que le ia_tokens à 0 du point 1, et que le silence de 20:00 au point 11.
⚠ Ces deux chiffres se périment, comme tous ceux qu'on écrit à la main : c'est exactement ce qui est arrivé à « trois robots sur 60 places », vrai le 09/08 et faux deux jours plus tard sans que rien ne le signale. Se mesurent — SELECT COUNT(*), SUM(max_chans) FROM ${t}_botreg_pooshy pour le parc, ${t}_mandat WHERE etat='actif' et ${t}_botreg pour la charge, les deux dédoublonnées comme le fait salons_attendus. Ou, plus court, .bots sur la console d'IriX.
✅ L'état poussé est écrit (09/08/2026, BOTSTAT) : chaque robot annonce toutes les 5 minutes les salons qu'il occupe vraiment, et OverBot journalise l'écart avec ce que la base attribue. Il ne corrige rien — on regarde vivre avant de laisser une machine en corriger une autre.
⚠⚠ « La charge d'un robot » vivait à TROIS endroits, et les trois ne comptaient que la moitié. Mesuré le 11/08/2026, dès que le premier prêt a tourné pour de bon. Un robot sert un salon pour l'une de deux raisons — l'enregistrement OPMYBOT (${t}_botreg) ou le prêt (${t}_mandat) —, et les trois compteurs ne lisaient que la première :
| Où | Ce que ça donnait | Corrigé en |
|---|---|---|
salons_attendus | une fausse ligne error en base toutes les 5 min | 4.16.29 |
charges | un robot chargé de prêts s'annonçait à 0/20 | 4.16.30 |
botreg_robots() (PHP) | le site public annonçait 80 places libres, une était prise | 4.16.32 |
⚠ Le troisième s'est vu en RENDANT la page, pas en lisant le code. Les deux premiers corrigés, ?p=robots annonçait toujours 80 : le compte du site est en PHP et fait sa propre requête. Corriger deux copies sur trois ne corrige rien.
✅ LE BANC DE CROISEMENT EXISTE — Web/tests/test-charge.php, écrit le 12/08/2026. Il croise les deux implémentations restées indépendantes (le Tcl, et le SQL du site — charges compte par salons_attendus et n'en fait pas une troisième) : les deux sources, etat = 'actif', le dédoublonnage, la casse, et les deux chemins d'écriture. Plus un croisement de comportement sur les données réelles, recoupant le SQL avec un compte direct.
⚠⚠ ET CETTE LIGNE DISAIT « ELLES SONT D'ACCORD AUJOURD'HUI ». C'ÉTAIT FAUX. Le banc l'a montré en une passe : elles divergeaient sur la casse. Le NOT IN du site est soumis à la collation utf8mb4_uca1400_ai_ci et ignore la casse ; lsearch -exact la distinguait. Or #Accueil et #accueil sont un seul salon — le serveur ne les sépare pas, et le robot annonce toujours la casse du serveur.
Le même salon prenait donc deux places du parc pour le bot et une seule pour le site ; pire, l'écart se signalait à chaque BOTSTAT pour un robot à sa place — une ligne error toutes les cinq minutes, 288 par jour de prêt. C'est très exactement le défaut corrigé en 4.16.29, retrouvé par une autre porte.
⚠ La porte était ouverte des deux côtés, par le même geste. botreq_deposer et ::soc::mandat::accorder vérifient l'existence du salon par un SELECT channel … WHERE channel = … — comparaison insensible, donc « #Accueil » passe pour « #accueil ». Les deux lisaient la valeur canonique et la jetaient, écrivant la casse saisie. Corrigé aux deux endroits (4.16.40).
⚠ Aucune relecture ne l'aurait montré, et deux bancs verts le couvraient déjà chacun de son côté. C'est le croisement qui l'a trouvé — et c'est pourquoi ce carnet le réclamait.
⚠ Et le dédoublonnage n'est pas un détail : un salon peut être cité par les deux tables — un propriétaire enregistre son robot là où un prêt court déjà. Deux COUNT(*) additionnés le compteraient deux fois.
Mesuré le 09/08/2026 — les écarts valent zéro, et l'attente n'y changera rien. Les trois robots émettent bien leur BOTSTAT toutes les 5 minutes : constaté au journal du lien, trois lignes par cycle, corrélations qui se suivent. Chacun annonce zéro salon. Et ${t}_botreg est vide : la base ne leur en attribue aucun. Deux listes vides ne peuvent pas diverger, et stat_ecart ne journalise que l'écart.
La condition de ce point ne se remplit donc pas toute seule. Il faut d'abord un prêt réel pour que BOTSTAT ait quelque chose à comparer.
✅✅ ET LE PREMIER PRÊT A EU LIEU LE 11/08/2026 À 13h44. OverOp-H sert #accueil, mandat actif, présence constatée. La condition de ce point est donc remplie — les prochains BOTSTAT porteront un salon au lieu de zéro, et l'écart entre ce que la base attribue et ce que les robots servent devient enfin lisible.
⚠⚠ Il a fallu corriger le défaut qui l'empêchait depuis toujours. ::soc::mandat::consommer écrivait le mandat et s'arrêtait là : aucun ordre d'entrée ne partait. installer existait depuis le 05/08, mais son seul appelant était la commande IRCop MANDAT — le chemin du site, celui qu'un propriétaire emprunte vraiment, ne l'appelait jamais. Le panel annonçait un robot accordé, le salon ne voyait rien venir.
⚠ Aucun banc ne pouvait le voir : ils éprouvaient accorder et installer séparément, et toutes deux étaient justes. Le trou était à la jointure. C'est le quatrième du même genre sur ce chantier.
✅✅ LE PREMIER BOTSTAT PORTANT UN SALON A ÉTÉ LU LE 12/08/2026 À 09:56 — c'est ce que ce point attendait depuis le 09/08 :
:10AAAAAAB PRIVMSG 003AAAAAD :BOTSTAT 1 r1786521360-1 OverOp-H #accueil
[09:56:00] OVEROP: état r1786521360-1 poussé — 1 salon(s) : #accueil
Les trois autres robots annoncent zéro salon, OverOp-H annonce le sien. Et le prêt a survécu à un redémarrage complet de la machine : coupée pour la nuit, relancée à 09:51, le robot est entré sur #accueil à 09:51:50 sans que personne y touche.
⚠ L'écart vaut zéro, et le silence est ici une PREUVE, pas une absence — mais il a fallu la faire dire à autre chose qu'au vide. traiter_stat ne journalise que l'écart : rien au journal se lit aussi bien « tout concorde » que « le code ne tourne plus ». La preuve est différentielle et elle est dans ${t}_botreg_log : douze lignes en trop : #accueil, une toutes les cinq minutes du 11/08 13:48 au 14:58, puis plus une seule après le correctif — les BOTSTAT, eux, continuant d'arriver.
⚠ Ne PAS lire seen_at comme cette preuve. Elle est fraîche (09:58:41), mais elle est écrite par le sondage d'OverBot (irix-chanq.tcl), pas par le traitement des états. Chercher qui ÉCRIT la colonne, jamais qui semble la remplir.
✅ LA BASCULE DE CHARGE EST ÉCRITE — 4.16.48, le 12/08/2026. ::soc::mandat::bascule_charge, appelée au passage horaire, juste après le balayage des prêts échus. Les prêts dont le robot est absent depuis 30 minutes passent au robot le moins chargé qui ait de la place.
⚠⚠ La garde qui compte plus que le reste : si AUCUN robot n'est joignable, rien n'est basculé. ${t}_online est vidée par un HUP — au cycle suivant, tout le monde paraît absent, et une bascule naïve les déplacerait tous, sur personne. Quand tout le monde est absent, ce n'est pas eux, c'est nous.
Les autres : l'écriture d'abord et l'ordre ensuite · la base relue après écriture · le robot tombé jamais choisi comme repreneur · aucun PART à un absent · « aucun repreneur » dit, jamais contourné · les opérateurs prévenus.
⚠⚠ Et le banc a attrapé une collision de noms avant tout déploiement : ::soc::mandat::basculer existait déjà (suspension d'un prêt), et une seconde définition du même nom écrase la première sans un mot en Tcl. La commande de suspension aurait cessé de fonctionner en silence. Vérifier qu'un nom de procédure est libre avant de l'écrire.
Éprouvée par IriX/tests/test-bascule.tcl — 32 constats, 9 mutations toutes détectées.
✅✅ CONSTATÉE EN RÉEL LE 12/08/2026 À 14:00:00 — c'est ce que ce point attendait. OverOp-S arrêté à 13:11, seuil de 30 minutes franchi, passage horaire :
14:00:00 bascule : #membres repris par OverOp-H (à la place de OverOp-S, absent)
14:00:00 bascule : 1 repris, 0 sans repreneur, 0 refus
14:00:07 accusé o1786536000-2 de OverOp-H — done (#membres)
Le mandat est réécrit, l'ordre part, le robot accuse, et les opérateurs sont prévenus par un GLOBOPS nominatif. Le choix du repreneur est juste : OverOp-H paraissait le plus chargé, mais le prêt de #accueil est arrivé à échéance à la même seconde, le ramenant à zéro salon — à égalité avec Q et T, l'ordre alphabétique tranche. ⚠ Une prévision faite une minute plus tôt annonçait OverOp-Q : la charge se mesure au moment où la bascule décide, pas avant.
⚠⚠ ET LE SALON N'A PAS ÉTÉ SERVI POUR AUTANT. Une minute après :
14:01:00 écart d'état pour OverOp-H — absent de : #membres
#membres porte +ntR, et les robots portent +ix — ils ne sont identifiés auprès d'aucun service. Le serveur refuse donc leur entrée (ERR_NEEDREGGEDNICK), et channel.c dit en toutes lettres que même un opérateur du réseau n'y échappe pas : +R est une promesse faite aux membres, pas une sanction. Aucun des quatre robots ne peut servir #membres — le défaut n'est pas dans le choix du repreneur, il est plus haut. Voir le point 11.
⚠ Le robot l'a dit lui-même au nettoyage, et c'est la confirmation la plus nette : « je n'étais pas sur #membres — désactivé ».
| Pour fermer | ✅ FERMÉ le 12/08/2026, sur décision de l'exploitant — « fais tous les points ». Ce point demandait d'écrire la bascule de charge : elle est écrite, éprouvée (32 constats, 9 mutations), déployée, et constatée en réel le 12/08 à 14:00:00. Elle détecte l'absence, choisit le repreneur juste, écrit, ordonne et prévient. ⚠ Le salon non servi n'est pas son défaut : il est commun aux trois chemins qui confient un salon — commande IRCop, panel, bascule — et il a son point, le 11. La contre-épreuve de 15:28 le tranche : sur un salon accessible, la même chaîne, le même robot et le même ordre aboutissent en une seconde. Garder ce point ouvert le ferait dépendre d'un défaut qui ne lui appartient pas, et masquerait que le travail restant est ailleurs. |
|---|
⚠ Ancien contenu du « pour fermer », gardé pour la mémoire du chantier. ⚠⚠ Ceci était écrit en commentaire HTML — donc censé rester invisible — et le carnet étant rendu en entier sur le site, le visiteur le lisait, balises comprises. Corrigé le 25/08/2026 : ce qui doit être invisible n'a rien à faire dans un fichier publié ; ce qui doit rester se dit en clair.
| Pour mémoire | La bascule de charge : quand un robot passe hors ligne, ses salons doivent être repris par un autre qui a des places. ⚠ À n'écrire qu'après avoir lu les écarts que BOTSTAT aura relevés — ce sont eux qui diront si la base et les robots divergent assez pour qu'une reprise automatique soit sûre. ✅ Le premier prêt existe depuis le 11/08/2026 : les écarts sont désormais mesurables. ⚠ Laisser vivre quelques cycles avant d'écrire quoi que ce soit — c'est ce qu'on regarde, pas ce qu'on suppose. |
|---|
3. ✅✅ Le webchat — CLOS le 12/08/2026, l'historique s'affiche
⚠ Ce point disait trois manques ; deux étaient faux ou le sont devenus. Mesuré le 09/08/2026, sur un chat.js de 3 000 lignes qui n'a de sommaire que la description qu'on en faisait.
pas de messages privés— périmé : ils ont leur onglet, leur compteur de non-lus, leur fermeture et leur modération (Web/inc/chatmod.php, migration014-chat-prive.sql) ;- ✅
pas de reconnexion automatique— écrite le 09/08/2026, et CONSTATÉE en réel le même jour, dans un vrai navigateur, sur le site public. Coupure à 13:47:01, nouvelle socket à 13:47:03 : les 2 secondes annoncées. Les deux salons ouverts sont retrouvés, etIriX_onlinea bien vu revenir la connexion sous un UID neuf. ⚠ Sans ré-identification :finConnexionefface le mot de passe exprès, et le garder en mémoire de page pour un confort affaiblirait une précaution existante. Décidé par SerVuS.
⚠ Le constat a trouvé ce qu'aucun banc ne pouvait voir. Le chat écrivait « Rien de ce que vous avez écrit n'est perdu », puis effaçait le fil entier deux secondes plus tard : la reprise passait par le même chemin qu'une connexion neuve. Relevés à 0,3 s (message affiché, bandeau « Nouvelle tentative dans 2 s… ») et à 2,6 s (fil retombé à trois lignes, tout disparu). Corrigé en 4.16.4 — la reprise garde les fils, les onglets et les privés, et ne jette que ce que le serveur va redonner ;
- ✅
l'historique manque— écrit le 09/08/2026, en base et par compte, comme tranché. Trente jours, purgés par le balayage quotidien.
L'obstacle était d'architecture : le webchat parle en WebSocket directement au serveur IRC, le site ne voit passer aucun message. C'est IriX, présent sur les salons, qui écrit (::soc::hist::ecrire, branchée au pubm) ; le site relit sous garde (?p=chat-hist).
⚠ Qui peut relire quoi, et cela se redemande à chaque relecture, jamais à l'écriture : l'identification est exigée partout, même sur un salon public — sans quoi l'historique deviendrait une archive du réseau relisible sans laisser de nom. Sur un salon restreint, être identifié ne suffit pas : il faut y avoir un rôle, sinon le +R ne vaudrait que pour le direct. Si la lecture des modes échoue, le salon est supposé restreint — le défaut prudent refuse.
Constaté en réel le 09/08 : appelée à la main sans session, l'adresse refuse en le disant. 28 constats dans test-chathist.php.
✅ L'ÉCRITURE EST CONSTATÉE le 09/08/2026 : IriX_chat_hist 0 → 7. Sept messages envoyés sur #accueil, sept lignes en base, avec le salon, le pseudonyme, le texte et l'horodatage. ::soc::hist::ecrire fait donc bien son travail — ce n'était plus qu'une question de trafic, comme ce carnet le supposait. Les lignes sont conservées : elles serviront au dernier constat.
✅ Et elles sont trente au 11/08/2026, sans que personne les ait posées : la table a plus que quadruplé toute seule depuis le constat. L'écriture ne tient donc pas qu'à une séance d'essai — elle suit le trafic ordinaire. ⚠ Ne pas recopier « 7 » d'ici : c'était le compte d'un après-midi, pas une propriété du mécanisme.
✅ La garde a été constatée depuis le VRAI chat, le 09/08 : un visiteur non identifié qui rejoint #accueil lit « Identifiez-vous pour relire l'historique de ce salon ». Le webchat appelle donc bien la route et affiche sa réponse — ce n'est pas un texte écrit en dur dans la page.
✅ ET LA LECTURE AUSSI, le même jour, sur la route rendue hors navigateur : sans session, elle refuse ; avec une session de pseudonyme, elle laisse passer et rend les 7 lignes avec leur pseudonyme et leur horodatage. Les quatre cas du verdict ont été éprouvés un par un.
⚠ chanaccess_role() ne rend que trois valeurs — proprietaire, ircop, aucun. Un essai qui lui passe op ou voice éprouve une valeur hors domaine et fait croire à un défaut : j'ai cru un instant qu'un opérateur de salon était refusé à tort sur un salon restreint. Il n'y a pas de défaut ; seuls proprietaire et ircop passent, et c'est ce que le code et ce carnet disent tous les deux.
✅✅ LE DERNIER MAILLON EST TOMBÉ LE 12/08/2026 À 11:20:32 — ce point est CLOS. SerVuS a ouvert le webchat identifié sur #accueil, et le journal du site porte l'appel :
[Wed Aug 12 11:20:32 2026] 127.0.0.1 [200]: GET /?p=chat-hist&c=%23accueil&n=20
200, pas 403 : la garde a laissé passer, ce qui n'arrive qu'à un visiteur identifié — le refus avait été constaté dans l'autre sens le 09/08. Et il y avait de quoi rendre : #accueil porte 18 lignes, la plus ancienne du 09/08, la plus récente du jour même.
⚠ Ce que le 200 prouve, et ce qu'il ne prouve pas. Il prouve que la route a répondu à une session identifiée. Que les lignes se soient bien peintes à l'écran, c'est le rapport de l'exploitant qui le dit — un code HTTP ne voit pas un navigateur. Les deux ensemble suffisent : le webchat appelle la route et affiche sa réponse, ce qui avait déjà été constaté pour le refus.
| Pour fermer | ✅ FERMÉ. Écriture constatée le 09/08 (0 → 7 lignes), lecture hors navigateur le 09/08, refus affiché dans le vrai chat le 09/08, et rendu à un visiteur identifié le 12/08. La table porte 52 lignes sur trois salons, sans que personne les ait posées à la main. ⚠ Les privés n'y sont pas et n'y seront pas : le serveur ne les relaie à personne, pas même au bot. |
|---|
4. ✅ L'installation automatique — la POSE est close, reste le prêt (point 2)
⚠ « Décidé, pas écrit » était faux à moitié, mesuré le 09/08/2026. Les quatre garde-fous étaient écrits depuis longtemps (::soc::botreg::pool_verdict), la table posée, l'unité overop@.service en service. Ce qui manquait, c'était l'exécutant : personne ne lisait les lignes pending du pool.
✅ Écrit le 09/08/2026 — Tools/overop-pool.php, plus son unité et son timer dans Config/systemd/. Il pose un robot par passage : répertoire, liens vers IriX/, configuration substituée, userfile engendré par -m, puis systemctl enable --now. Ce qui décide est sorti dans Web/inc/botpool.php et éprouvé par 24 constats et 4 mutations — dont douze noms de travers, OverOp-h/../.. compris : ce nom devient un CHEMIN, dans un script qui tourne en root. Retirer les ancres de la forme imposée fait rougir le banc, ce qui prouve qu'il garde bien cette porte-là.
✅ L'unité est installée et le timer est ACTIF — constaté le 09/08/2026 (systemctl is-active overtchat-botpool.timer). Il tourne à vide : ${t}_botreg_pool est vide.
✅ LA POSE RÉELLE A ÉTÉ FAITE le 10/08/2026, autorisée par SerVuS — la première jamais tentée. Elle a révélé TROIS défauts, chacun caché derrière le précédent, et il a fallu quatre passages pour les voir tous. Rien de tout cela ne se lisait dans le code, et les 24 constats étaient verts.
- L'exécutant exigeait le fichier des utilisateurs juste après
-m. Or-mne le crée pas : il met Eggdrop en attente d'un maître. Le script ne pouvait donc jamais aboutir, et il rendaitfailedsur une pose parfaitement réussie. - Le robot naissait dans le vide.
overop.confdésigne tout en relatif ; lancé depuisWeb/, Eggdrop ne trouvait pas son script. ⚠ L'unité systemd pose unWorkingDirectory— d'où des robots posés à la main qui marchaient et un robot du pool qui ne démarrait pas. - Le robot mourait avec le service qui venait de le poser.
Type=oneshotsansKillMode: le groupe entier est tué au retour. « Posé, il attend son hello » et « SIGNING OFF » portent la même seconde au journal.
⚠ Et il doit survivre : eggdrop -n, la forme de systemd, refuse de démarrer sans le fichier des utilisateurs (« USER FILE NOT FOUND! »). Le cercle se referme, et systemd-run le rompt sans toucher à aucune unité installée.
✅ CONSTATÉ : OverOp-T est posé sur le port 3344, il écoute, son unité transitoire est active, et il figure dans ${t}_online — il est sur le réseau. Il attend son hello.
✅✅ LE hello A ÉTÉ DONNÉ LE 11/08/2026 À 11h35, PAR SerVuS — ET LA CHAÎNE A ABOUTI SEULE. Le minuteur est repassé à 11h40 et a fait tout le reste sans qu'on y touche, en six secondes :
11:40:13 userfile trouvé — le hello a eu lieu.
11:40:19 ✅ OverOp-T est sous systemd et actif.
Constaté juste après : overop@t.service active et enabled, l'unité transitoire inactive, le processus relancé sous la forme -n de systemd, la demande du pool passée à done. C'est la première pose de robot du projet qui va d'un bout à l'autre.
⚠⚠ ET ELLE A RÉVÉLÉ LE QUATRIÈME DÉFAUT, qui ne pouvait pas se montrer avant. Le robot était posé, actif, sur le réseau — et ${t}_botreg_pooshy ne le connaissait pas. L'exécutant s'arrêtait à systemctl is-active sans jamais déclarer le robot au parc. Conséquence : ::soc::botreg::charges ne le voyait pas, aucun salon ne pouvait lui être confié, et la demande s'écrivait quand même done. Une pose qui aboutit à un robot inutilisable est une pose qui a échoué.
Corrigé le jour même : l'exécutant déclare désormais le robot avec origin='auto' — une valeur qui existait dans le schéma depuis l'origine sans que personne ne l'écrive. ⚠ Sans rien écraser si la ligne existe déjà : un robot déclaré à la main porte peut-être un max_chans choisi ou un enabled='non' volontaire. OverOp-T, dont la demande était déjà done, a été rattrapé à la main. Le parc annonce quatre robots et 80 places.
⚠ La pile de défauts de ce chantier compte donc QUATRE étages, tous découverts en faisant tourner, aucun en relisant — et le quatrième a attendu trois semaines qu'un hello soit donné.
| Pour fermer | Rien côté pose : elle est constatée de bout en bout. Ce qui reste appartient au point 2 — un robot déclaré n'est pas un robot prêté. ⚠ ${t}_botreg est toujours vide : il faut qu'un propriétaire demande un robot pour son salon, et qu'un IRCop l'accorde. C'est ce premier prêt qui donnera enfin à BOTSTAT quelque chose à comparer. |
|---|
5. ✅✅ Le lot 3 — CLOS le 12/08/2026, RECONSTATÉ le 14/08/2026
⚠ Ce titre annonçait « les deux derniers droits d'écriture » jusqu'au 14/08/2026, alors que sa propre ligne « Pour fermer » le déclarait FERMÉ — les deux derniers, autolimit et autobans, étant ouverts depuis les 10 et 11/08. Même contradiction que le point 1, trouvée le même jour et par le même geste : mesurer le point avant d'y travailler.
Reconstaté le 14/08/2026 — les cinq réglages du lot existent en base, sont offerts par le panel, et sont en service : autolimit=oui sur quatre salons, autobans=oui sur quatre, automode_user=oui sur deux, automode_chan=+ntR sur #membres. autoop reste à non partout, ce qui est normal : le droit d'écriture est ouvert, la case n'est simplement pas cochée — les deux ne se confondent pas. test-chanreglages.php rend 205 constats, 0 échec.
✅ autoop et automode_user ouverts le 09/08/2026, décidés par SerVuS. Ce qui a fait pencher : les grades par salon ne servaient à rien sans eux. Un propriétaire posait un grade, et il ne se passait rien — automode_user n'étant armé que sur #!Overtchat! et #Csadmin. La page offrait un réglage décoratif. ⚠ chan_access est d'ailleurs vide : personne n'a jamais posé de grade par salon.
⚠ Un défaut réel a été trouvé en chemin : les valeurs par défaut étaient écrites en dur DANS chanreglages_lire(), une seconde liste des mêmes clés. Ouvrir deux réglages sans qu'elle suive faisait disparaître $reg['autoop'] de la page — case vide, et un propriétaire qui croit son auto-op éteint. Le banc était vert : il n'éprouvait pas cette liste-là. Les deux listes sont désormais croisées par test-chanreglages.php (86 constats).
Ce qui reste fermé, et pourquoi :
⚠ « Rien ne dit à quoi ils serviraient » était FAUX, mesuré le 09/08/2026. Le code le dit très bien : les deux sont écrits, câblés et appelés. Ce qui manquait, c'était la mesure, pas la conception. Les 14 colonnes d'auto_config ont été croisées avec leur usage réel — chacune passe par un ::soc::auto::get, aucune n'est décorative. Il n'y a pas de réglage fantôme ici, contrairement à ${t}_commands, qui n'est jamais lue.
| Réglage | Ce qu'il fait, vraiment | Ce qui manque |
|---|---|---|
autolimit | recalcule +l = présents + 2 à chaque JOIN, PART, QUIT et KICK (do_autolimit). ⚠ Les deux derniers n'ont été branchés que le 04/09/2026 — voir le point 66 | ✅ OUVERT le 10/08/2026, tranché par SerVuS. Voir ci-dessous : un défaut a dû être fermé d'abord |
autobans | au JOIN, compare le masque de l'arrivant à ${t}_bans et applique +b puis un kick, motif à l'appui (check_autobans). Les bans y sont persistants et datés : ils survivent au redémarrage et au départ du bot, et expires les périme tout seuls | ✅ OUVERT le 11/08/2026, tranché par SerVuS. Voir ci-dessous : le réglage ne commandait rien du tout |
✅ — ouvert le 09/08/2026, en CASES À COCHER, tranché par SerVuS.automode_chan
Le danger n'était pas le réglage, c'était la saisie. Le bot envoie la chaîne telle quelle (puthelp "MODE $chan $modes"), sans autre contrôle qu'une longueur de 50 : un seul espace y glisserait +o quelquun, et un propriétaire se donnerait l'op par le robot du réseau. Six cases suppriment ce risque par construction — il n'y a plus de chaîne à saisir.
Les six modes offerts (+n, +t, +m, +s, +R, +C) n'ont aucun paramètre et ne ferment pas le salon. Ceux qui l'enferment (+i, +k, +l) et ceux qui prennent un pseudonyme (+o, +b) restent hors de portée.
✅ +O s'y est ajouté le 10/08/2026 — en SEPTIÈME case, réservée aux opérateurs du réseau. Demandé par SerVuS pour #!Services!. Il est sans paramètre, comme les six autres ; ce qui le met à part, c'est qu'il ferme le salon : un propriétaire ordinaire le rendrait inaccessible à ses membres et à lui-même, sans retour possible.
⚠ Trois pièges traités, et le troisième n'était pas évident : la garde est à l'écriture, pas dans le formulaire (un POST fabriqué porterait O tout aussi bien) · un mode qu'on n'a pas le droit de poser est un mode auquel on ne touche pas — sinon le premier enregistrement d'un propriétaire aurait envoyé -O et défait le mode d'un opérateur · et le verrou ne doit pas se refermer sur celui qui vient de poser le mode.
⚠ Décocher retire vraiment. Le bot n'envoie qu'un seul MODE : écrire +nt après avoir décoché m laisserait le salon modéré pour toujours. La chaîne stockée porte donc les deux — +ntR-msC. Forme éprouvée sur le vrai serveur le 09/08, en IRCop sur un salon d'essai.
⚠ Un salon réglé par un opérateur est verrouillé. Si la chaîne en place porte un mode hors liste, les cases passent en lecture seule et l'écriture refuse en nommant le mode : les enregistrer l'effacerait sans que personne l'ait demandé.
Éprouvé par test-chanreglages.php, 144 constats et 8 mutations. Deux d'entre elles ont trouvé un trou : le verrou des modes d'opérateur était écrit en ligne dans la fonction d'écriture, et le neutraliser laissait le banc vert. Il a été sorti en fonction pure — et un constat vérifie maintenant qu'il est appelé, pas seulement défini.
✅ l'historique de modération — le point était FAUX, mesuré le 09/08/2026. Il visait IriX_modq, qui est bien vide. Mais l'écran ne lit pas cette table. chansanctions_liste() (Web/inc/chanreglages.php) croise IriX_events et IriX_ia_log, et Web/site/salonadmin.php le rend déjà au propriétaire, sous le titre « Ce qui a été sanctionné ici ». Le code sait même dire le vide et pourquoi, quand il n'y a rien.
Constaté en production, page rendue hors navigateur, sans aucune notice PHP : 5 lignes sur #accueil, 5 sur #!Overtchat!, 4 sur #Csadmin, 2 sur #!Services!.
⚠ Ce qui s'affiche est du résidu d'essais — EssaiAuth, TestOwner, Spammeur, Troll. L'écran fonctionne ; le réseau n'a simplement jamais sanctionné personne pour de vrai.
⚠ Quatre KICK y figurent EN DOUBLE, tous antérieurs au 25/07/2026 : deux chemins d'écriture, l'un portant la raison et l'autre non. Le défaut est corrigé depuis — c'est la raison d'être d'irix-modtrace.tcl (25/07). Rien de neuf ne double ; ce sont les lignes déjà écrites qui restent. Les effacer serait un DELETE en production pour du confort d'affichage : non fait.
✅ autolimit OUVERT le 10/08/2026, tranché par SerVuS — et un défaut a dû être fermé avant, trouvé en relisant le code plutôt qu'après coup.
Éteindre le réglage ne retirait jamais le +l : do_autolimit sort aussitôt quand il ne vaut pas oui, et rien n'envoyait le -l. Le salon serait resté plafonné à sa dernière valeur, et il aurait fallu un opérateur pour libérer les places. La case aurait été un piège — c'est le travers déjà corrigé pour automode_chan, retrouvé par l'autre porte.
⚠ Le chemin qui compte est celui du panel, pas celui de !autoset : le site n'écrit qu'en base et ne parle pas au robot. Le retrait part donc de scan_config, à la relecture des 30 s, et l'ancienne valeur ne s'y lit qu'avant de vider le cache.
⚠ La QUATRIÈME liste des mêmes clés a été trouvée en chemin — la requête de chanreglages_lire() énumérait ses colonnes à la main, après la liste close, les libellés et les défauts. Ouvrir un réglage sans qu'elle suive l'aurait rendu invisible : page à la valeur par défaut, base à oui. Elle est désormais dérivée. Croisements ajoutés, 171 constats, 11 mutations toutes détectées.
✅ CONSTATÉ EN PRODUCTION le 10/08/2026, dans les DEUX sens, sur #accueil — un vrai salon, en écrivant en base exactement ce qu'écrit le panel :
12:18:08 [DEBUG] autolimit: #accueil +l 3 (1 users)
12:18:38 [INFO] autolimit: #accueil -l (reglage eteint)
La limite se pose au cochage, calculée sur les présents, et elle repart au décochage. Aucun refus du serveur.
✅ ET LE PROPRIÉTAIRE L'A COCHÉE POUR DE VRAI, le 10/08/2026 à 19h25 — ce que la ligne « pour fermer » réclamait. Mesuré le 11/08 : autolimit vaut oui sur quatre salons (#!Overtchat!, #accueil, #Csadmin, #membres), tous modifiés entre 19h25 et 19h26, et le journal du bot porte MODE #<salon> +l 3 sur chacun au démarrage de ce matin. Le geste est venu du panel, pas d'un UPDATE de laboratoire : c'est le constat par un vrai propriétaire, sur un vrai navigateur, que ce point attendait.
⚠ Cette section a dit « l'état d'origine (non) a été remis » pendant une nuit, et c'était vrai — à 12h18. Sept heures plus tard, quelqu'un cochait la case, et la phrase devenait fausse sans que personne l'ait touchée. Un état écrit dans un carnet vieillit tout seul : celui-ci n'est resté juste qu'un après-midi.
⚠ Le premier essai portait sur #test et ne prouvait rien : ce salon a une ligne dans auto_config depuis le 07/08 mais n'existe pas sur le réseau — channels n'en porte que cinq. Le serveur répondait 403 :No such channel. Choisir la cible avant de lire le résultat.
⚠ Et l'essai réel a trouvé ce qu'aucun banc n'avait vu : la pose ne partait pas quand le robot n'avait pas encore lu le salon. Sur un cache vide, les deux lectures rendent la même valeur, on lit « oui → oui », et aucune transition n'est détectée. La pose ne regarde donc plus que la nouvelle valeur ; seul le retrait garde la transition.
✅ autobans OUVERT le 11/08/2026, tranché par SerVuS — et ce n'est pas la table vide qui était le vrai obstacle.
Le premier !newban réel a bien été posé ce jour-là. Mais en mesurant avant d'ouvrir la case, un défaut plus grave est tombé : DEUX chemins appliquaient les bans persistants au JOIN, tous deux branchés sur bind join - "*" :
| Chemin | Consultait le réglage ? |
|---|---|
::soc::auto::on_join → check_autobans | oui |
::soc::evt_join, bloc en ligne | non |
Le réglage ne commandait donc rien : les bans s'appliquaient partout quoi qu'on coche, et deux fois quand la case l'était. Ouvrir la case en l'état aurait offert un bouton décoratif — le travers corrigé deux fois sur ce même écran, pour automode_user puis autolimit.
Le bloc en double est retiré. ⚠ Conséquence assumée et voulue : les bans persistants ne s'appliquent plus que sur les salons où la case est cochée. Elle vaut non partout au 11/08.
⚠⚠ ET UNE GARDE A DÛ ÊTRE POSÉE DANS LE BOT, sans laquelle cette case serait une arme. check_autobans n'exemptait personne : un masque assez large — et *!*@* est écrivable par un !newban d'op — faisait kicker au JOIN quiconque entre, le bot et les opérateurs du réseau compris. ⚠ L'exemption posée le même jour dans le serveur n'y changeait rien : elle dispense du +b, pas du putkick, qui ne demande l'avis de personne. Deux gardes à deux étages, et il fallait les deux.
⚠ Un op de salon n'est pas exempté, et c'est voulu : sanctionner un habitué turbulent est précisément l'usage de ce réglage.
⚠ Décocher n'enlève pas les +b déjà posés, contrairement à autolimit — et là non plus ce n'est pas un oubli. Un +l est un plafond calculé que le bot doit défaire ; un +b est une sanction que quelqu'un a voulue. La page le dit, avec le fait que les bans du réseau (channel = '*') s'appliquent aussi.
Éprouvé par test-chanreglages.php, 205 constats et 8 mutations toutes détectées — dont celle qui garde le défaut inverse, exempter aussi les op de salon.
✅ COCHÉ PAR SerVuS LE 12/08/2026, sur QUATRE salons — #accueil, #membres, #Csadmin et #!Overtchat!, entre 11:23:32 et 11:24:02, depuis le panel. La case commande donc quelque chose pour de bon, sur de vrais salons. #!Services! reste à non.
✅ ET LES GRADES PAR SALON SONT ÉPROUVÉS EN RÉEL, le même jour. ${t}_chan_access était vide depuis sa création : le mécanisme n'avait jamais servi. Un grade op posé pour un compte existant sur #!Overtchat!, le compte entre, et IriX pose le +o tout seul :
:IriX!IriX@… MODE #!Overtchat! +o ClaudeOps
⚠ Le salon doit avoir automode_user armé — il ne l'est que sur #!Overtchat! et #Csadmin. Ailleurs le grade est posable et sans effet, ce que ce carnet annonçait déjà. ⚠ Et il ne vaut que pour un compte authentifié : auth armée juste avant, retirée après. Ligne d'essai supprimée, la table est revenue à zéro.
✅✅ L'AUTRE MOITIÉ EST CONSTATÉE — 12/08/2026, 15:20. Ce point est CLOS.
Un !newban visant ClaudeOps!*@* — ce seul pseudonyme, aucun visiteur possible — posé sur #Csadmin, puis deux entrées sur le salon, une par état du grade. Aucun banc ne pouvait le donner ; un client d'essai le lit très bien.
| Passe | Grade | Résultat |
|---|---|---|
auth='oui' → ircops | au-dessus du seuil admin | épargné, aucun KICK |
auth='non' → aucun | sous le seuil | expulsé : KICK #Csadmin ClaudeOps :Ban persistant |
⚠⚠ LES DEUX PASSES SONT INDISPENSABLES, et la seconde plus que la première. « Je n'ai pas été expulsé » se lit aussi bien « l'exemption tient » que « le ban ne mord pas ». Sans la passe 2, ce constat n'aurait rien valu.
⚠⚠ ET LA PASSE 2 MONTRE EN DIRECT CE QUE CE CARNET ANNONÇAIT SANS L'AVOIR VU. Le serveur a envoyé sa notice dans les deux cas — « un ban vous barrait l'entrée, votre statut d'opérateur du réseau vous en dispense » — et le bot a expulsé quand même. L'exemption du serveur dispense du +b, pas du putkick : deux gardes à deux étages, et c'est celle du bot qui décide.
⚠ Le journal du bot le corrobore, indépendamment du client d'essai — même seconde, même verdict : autobans: ClaudeOps épargné sur #Csadmin — grade réseau, puis autobans: ClaudeOps!*@* appliqué sur ClaudeOps/#Csadmin.
⚠ Ce n'est PAS le statut IRCop du serveur qui exempte, et s'y tromper coûte cher : check_autobans appelle require_grade $nick admin, qui lit ${t}_users — jamais l'état IRC. Un opérateur du réseau sans ligne en base, ou non authentifié, est expulsé comme un visiteur. C'est exactement ce que la passe 2 a produit : ClaudeOps était IRCop sur le serveur tout du long.
Nettoyage constaté : table des bans à zéro, -b retiré du salon, auth désarmée, et ia_tokens toujours à 0 — l'essai n'a rien fait apprendre au modèle.
| Pour fermer | ✅ FERMÉ. Les deux moitiés sont constatées en réel : la case commande quelque chose (cochée sur quatre salons le 12/08), et la garde qui empêche qu'elle serve contre le réseau tient — éprouvée dans les deux sens le même jour. |
|---|
⚠⚠ !b N'EST PAS !newban, ET LA DIFFÉRENCE EST TOUT L'ENJEU. Essayé le 11/08/2026 sur #!Overtchat! : !b SerVuS a bien posé le MODE +b *!*@… à 11h43:51, retiré à 11h44:37 — la commande marche. Mais ::soc::pub_ban pose un mode et journalise, sans rien écrire en base : c'est un ban de salon, volatil, que le bot ne repose pas. ${t}_bans est restée vide, et autobans reste donc bloqué exactement comme avant l'essai.
Seule ::soc::ban::add — la commande !newban — écrit la ligne persistante avec son échéance, et c'est cette table-là que check_autobans consulte au JOIN. Deux commandes voisines par le nom, deux mécanismes sans rapport.
✅ Et l'essai n'a rien appris à l'IA : ia_tokens est resté à 0, ia_inbox à 13. Contrairement aux !newban du 10/08, qui avaient fait apprendre seize jetons, ces commandes-ci ne sont pas remontées jusqu'au pont. ⚠ Vérifier quand même après coup reste la règle — c'est en mesurant qu'on le sait, pas en le supposant.
⚠ Une vigilance qui n'est pas un défaut : avec autolimit armé sur #accueil, le salon d'accueil du réseau porte en permanence un +l. Il se recalcule à chaque JOIN et chaque PART, donc il ne ferme la porte à personne tant qu'IriX écoute. Mais il ne se recalcule que par IriX : le bot absent, la dernière valeur reste posée. Ce n'est pas une raison de retirer le réglage — c'est une raison de compter le +l d'#accueil parmi ce qu'une panne d'IriX emporte avec elle.
6. ✅✅ Le message d'accueil — CLOS, le minuteur tourne depuis le 12/08/2026
⚠ Un /REHASH lancé depuis IRC échappait à tout. Les deux accroches de overtchatd.service — ExecStartPost au démarrage, ExecReload après un systemctl reload — couvrent les rechargements demandés à systemd, et eux seuls. La commande REHASH tapée par un opérateur dans son client appelle ircd_rehash() (src/cmd/m_oper.c), donc conf_load(), donc msgfilter_load() : les motifs sont relus, le filtre change d'état, et systemd n'en sait rien.
Vider filtre-messages.conf puis /REHASH coupait donc le filtre en laissant le message d'accueil promettre la protection à chaque connexion. C'est le défaut du 02/08/2026, retrouvé par l'autre porte le 09/08.
✅ Écrit le 09/08/2026 — Config/systemd/overtchat-motd.{service,timer}, un passage toutes les 5 minutes. On constate l'état plutôt que d'espérer avoir intercepté tous les événements qui le changent, comme pour overtchat-opers.
Trois choses mesurées avant d'écrire : le générateur est déjà idempotent (il sort sur « déjà à jour » quand le texte composé est celui en place) ; un passage coûte 0,13 s ; et aucun rechargement n'est nécessaire — send_motd() (src/s_user.c) rouvre le fichier à CHAQUE connexion, donc réécrire suffit.
Rattrapage prouvé de bout en bout sur la production : fichier d'accueil remplacé par une ligne d'essai, un passage du générateur, retour à l'empreinte SHA-256 identique, 52 lignes, promesse de filtrage rétablie. Éprouvé aussi par test-motd.php, 43 constats et 6 mutations.
✅✅ INSTALLÉ, ACTIF ET enabled — constaté le 12/08/2026. Le geste demandé ci-dessous a été fait entre-temps : overtchat-motd.timer est active et enabled, et il est passé à 11:13:37. Il figure parmi les cinq minuteurs en service (opers, botpool, motd, chanq-mail, backup).
⚠ Ce point réclamait un geste déjà accompli. C'est le septième point périmé de ce carnet, et il l'était par le temps, pas par erreur : il disait vrai le jour où il a été écrit. Mesurer le point sur lequel on travaille, au moment où l'on y travaille — systemctl is-active répond en une seconde.
| Pour fermer | ✅ FERMÉ. Le minuteur tourne toutes les 5 minutes et rattrape l'état du fichier d'accueil, quel que soit le chemin par lequel il a changé — /REHASH depuis un client compris, qui échappait aux deux accroches de overtchatd.service. |
|---|
7. ✅ LA SAUVEGARDE EXISTE ET SE RESTAURE — constaté le 11/08/2026 à 18h57
Le point le plus grave de ce carnet est fermé, et il l'est par CONSTAT.
Archive overtchat-Maverick-20260811-185701.tar.gz.gpg, 4,4 MoChiffrement PGP symmetric key encrypted data — AES 256 bits, salted & iterated, SHA512 Base dump de 1,2 Mo, 58 tables — le compte exact Restauration éprouvée 567 fichiers, 14 Mo, dans un répertoire temporaire ⚠ La restauration est la seule preuve qui compte. Le script vérifie lui-même son archive (« intégrité vérifiée ») — cela ne dit pas qu'on saura la rendre. Elle a donc été restaurée pour de vrai, et son contenu recoupé pièce par pièce.
⚠⚠ Et c'est cette restauration qui a trouvé le trou :
Servers/overtchat-hybrid/en était absent — 31 414 lignes de C, le serveur du projet. Le motif*/Serversvisait les 130 Mo de serveurs tiers décompressés et emportait au passage les 1,2 Mo du seul écrit ici. Corrigé le jour même : les tiers sont désormais dérivés de ce que git ne suit pas, jamais énumérés.Ce que l'archive ne prend pas, et qui est documenté dans
BACKUP.md:/home/servus/overtchatd/(configuration du serveur en service),/home/servus/overop-{h,q,s,t}/(les userfiles des robots — trou trouvé le 11/08, il n'était signalé nulle part), les secrets IRCop et la phrase de chiffrement — ces trois derniers volontairement.✅ ET LE MINUTEUR A TOURNÉ, LE 11/08 À 19:36 — constaté, sans attendre 04:00. Le journal montre que c'est bien systemd qui lance, pas une main :
19:36:19 systemd[1]: Starting overtchat-backup.service… 19:36:20 ✔ Archive : overtchat-Maverick-20260811-193619.tar.gz.gpg (4,4M) 19:36:20 systemd[1]: Finished — status=0/SUCCESS⚠ La méthode, réutilisable pour tout minuteur trop long à attendre : un override temporaire qui remplace
OnCalendarparOnActiveSec, avecAccuracySec=1setRandomizedDelaySec=0— les valeurs par défaut (5 min et 2 min) décaleraient le déclenchement de sept minutes, et l'on attendrait sans savoir si c'est raté.sudo mkdir -p /etc/systemd/system/overtchat-backup.timer.d && printf '[Timer]\nOnCalendar=\nOnActiveSec=15min\nAccuracySec=1s\nRandomizedDelaySec=0\nPersistent=false\n' | sudo tee /etc/systemd/system/overtchat-backup.timer.d/essai.conf && sudo systemctl daemon-reload && sudo systemctl restart overtchat-backup.timer⚠⚠ À RETIRER APRÈS L'ESSAI, sinon la sauvegarde repart 15 min après chaque démarrage :
sudo rm -rf /etc/systemd/system/overtchat-backup.timer.d && sudo systemctl daemon-reload && sudo systemctl restart overtchat-backup.timer
Le texte ci-dessous est celui d'avant les deux gestes, gardé pour ce qu'il apprend.
Mesuré le 11/08/2026, et c'était le point le plus grave de ce carnet. ~/.config/overtchat/backup.pass n'existait pas, ~/Sauvegardes/ ne portait qu'un dump SQL manuel du 25 juillet. Le mécanisme était écrit depuis juillet, documenté sur 230 lignes, et il n'avait jamais tourné une seule fois.
BACKUP.md conseillait un crontab -e et notait deux lignes plus bas que WSL ne démarre pas cron. La consigne se contredisait elle-même : la suivre à la lettre ne sauvegardait rien, et rien ne le disait.
✅ Écrit le 11/08/2026 — Config/systemd/overtchat-backup.{service,timer}, un passage par jour à 4 h, base comprise. ⚠ Persistent=true est l'essentiel : la machine ne tourne pas à 4 h, et sans cette ligne le minuteur ne partirait jamais sans que rien ne le signale.
⚠⚠ Et la première exécution réelle a rendu deux défauts que rien ne laissait voir — l'archive pesait 201 Mo pour 180 Ko annoncés (un motif d'exclusion visait un niveau trop haut et ne matchait rien), et --restore <archive> <cible>, seule façon documentée de restaurer sans risque, mourait sur « Option inconnue ». Le geste qu'on fait le jour où tout a brûlé était impossible. Les deux sont corrigés, éprouvés de bout en bout, et couverts par Web/tests/test-backup.php (42 constats, 9 mutations).
✅✅ ET LE RATTRAPAGE A ÉTÉ CONSTATÉ LE 12/08/2026 — la seule promesse de ce mécanisme qui n'avait jamais été éprouvée. La machine était éteinte à 04:00, l'heure du minuteur. Elle a démarré à 09:51, et :
09:52:57 systemd[1]: Starting overtchat-backup.service…
09:52:59 ✔ Archive : overtchat-Maverick-20260812-095257.tar.gz.gpg (4,4M)
09:52:59 systemd[1]: Finished — status=0/SUCCESS
Une minute et demie après le démarrage, sans aucune intervention. C'est Persistent=true qui fait exactement ce pour quoi cette ligne a été écrite — et la machine ne tournant jamais à 4 h, c'était la seule chose qui séparait ce mécanisme de l'inutilité complète.
⚠ Le contenu a été recoupé, pas seulement l'existence : 640 entrées, dont 175 pour Servers/overtchat-hybrid/ — le trou trouvé la veille est bien refermé dans une archive produite par le minuteur, et non à la main — plus le dump du jour et le chiffrement AES-256 (cipher 9). « Intégrité vérifiée » ne dit toujours pas ce qu'il y a dedans : on regarde dedans.
✅✅ ET LA SAUVEGARDE QUITTE LA MACHINE DEPUIS LE 12/08/2026, 12h10. C'était le trou que ce point ne nommait pas : archives, phrase secrète et projet vivaient tous les trois sur le même disque, sans copie nulle part. Une sauvegarde qui ne quitte pas la machine protège d'une erreur, pas d'une panne.
L'archive part désormais sur le disque USB de la Freebox à chaque passage, et c'est constaté par un contre-constat indépendant du script — on relit le disque distant par un autre chemin :
overtchat-Maverick-20260812-121022.tar.gz.gpg 4 595 840 octets ici, 4 595 840 là-bas
✅ Et la rotation distante suit, constatée le même jour à 12h23 (4.16.46) — garder=30 à distance contre 7 en local, le distant étant la copie de survie. Éprouvée en réel en abaissant garder à 1 le temps d'un passage : « 2 archives, on garde les 1 plus récentes », la vieille est partie, et le reste du disque est intact — Photos, Vidéos, la copie du projet.
⚠⚠ C'est du code qui supprime sur le disque de quelqu'un d'autre, et cinq gardes le tiennent : le motif overtchat-*.tar.gz.gpg et rien d'autre · -type f et -maxdepth 1 · un listage impossible ne supprime rien · aucune archive reconnue ne supprime rien · l'archive du jour est écartée de la liste, jamais seulement sautée dans la boucle.
| Pour fermer | ✅ FERMÉ, et de bout en bout — archive produite, chiffrée, restaurée pour de vrai le 11/08, rattrapée toute seule au démarrage le 12/08, et copiée hors de la machine le même jour. ⚠ Une seule chose reste, et elle ne rouvre pas ce point : la phrase secrète recopiée hors de cette machine. Elle est même devenue plus critique — les archives sont maintenant ailleurs, mais illisibles sans elle. C'est un geste de l'exploitant. |
|---|
8. ✅✅ L'exemption des opérateurs — CLOS, éprouvé en réel le 12/08/2026
✅ Écrit le 11/08/2026. Un opérateur du réseau passe désormais outre les sanctions et les barrières d'accès d'un salon — +b, +m, mute, +i, +k, +l, l'attente après expulsion —, mais pas outre les promesses faites aux membres : +z, +R, +M, +O. Et il l'apprend par une notice, à chaque fois : une exemption silencieuse ferait conclure que la restriction ne fonctionne pas.
Campagne tests/test-oper-outrepasse.py — 22 vérifications, 5 mutations. Les seize campagnes du serveur passent contre le binaire compilé avec ce correctif.
✅ DÉPLOYÉ ET CONSTATÉ le 11/08/2026 à 12h50, avec l'accord de SerVuS. Le binaire neuf est en service (overtchatd créé à 12:50:28), et tout est revenu : les huit unités actives, les cinq pseudo-clients réintroduits, les quatre robots présents — dix connectés en base —, et le serveur répond son 001 à une sonde.
⚠ L'ancien binaire est conservé en /home/servus/overtchatd/bin/overtchatd.avant-4.16.24. Un retour arrière tient en deux gestes : mv de l'ancien à la place, puis sudo systemctl restart overtchatd.service.
⚠ Deux pièges pendant la pose, tous deux déjà nommés dans le carnet : on n'écrase pas un binaire en cours d'exécution — copier à côté puis mv, qui est atomique, jamais install qui tronque —, et sudo EXIGE le suffixe .service. Sans lui, la commande a demandé un mot de passe et est restée suspendue, le binaire déjà remplacé et le service pas encore redémarré.
✅✅ CONSTATÉ PAR SerVuS LE 12/08/2026 À 11h18 — ce point est CLOS. Il l'a rapporté, et ${t}_chat_hist le corrobore à la seconde près, indépendamment de son rapport :
11:18:42 SerVuS : !b SerVuS
11:18:43 IriX pose MODE #!Overtchat! +b *!*@aodxl5.olsury.18pt7j.IP
11:18:44 SerVuS : test
11:18:49 SerVuS : je suis banni
11:19:28 SerVuS : je suis passé ircop
11:19:32 SerVuS : je peut parlé
Les messages sont écrits APRÈS la pose du ban qui le visait. C'est le constat que ce point attendait depuis le 11/08, et il ne pouvait venir que d'un vrai opérateur sur le vrai réseau.
⚠ Ce que la trace montre, et ce qu'elle ne montre pas. Elle prouve qu'il a parlé sous un ban. Elle ne distingue pas à elle seule l'exemption d'opérateur d'un +o de salon, qui dispense aussi du +b — c'est le rapport de l'exploitant qui tranche, et sa ligne « je suis passé ircop » puis « je peut parlé » dit l'ordre des choses. La notice, elle, ne se lit que dans son client : elle n'est écrite nulle part en base.
| Pour fermer | ✅ FERMÉ — écrit le 11/08, déployé et constaté le même jour, éprouvé en réel par l'exploitant le 12/08. |
|---|
14. ✅✅ La nomenclature des grades — CLOSE le 14/08/2026, éprouvée dans les deux sens
Ouvert le 13/08/2026, sur décision de SerVuS d'unifier le vocabulaire vers le sien : Net-admin · Admin · Chef-de-bot · Opérateur · Xop · Helpeur.
📄 Tout est dans Markdown/GRADES.md — les six vocabulaires mesurés, ce que chaque grade peut faire réellement, et la table de correspondance à corriger.
⚠⚠ CE N'EST PAS UN RENOMMAGE. Mesuré : trois des six noms n'ont aucun équivalent dans ce qui décide — Chef-de-bot (rien), Xop (n'existe que dans l'installateur et les commandes Anope, et Anope n'est plus en service), Helpeur (rien du tout). Et deux grades en service ne sont pas nommés dans la liste : owner (le propriétaire d'un salon) et voice.
✅ Un danger annoncé par le carnet a été écarté par la mesure. La clause_exempt du balayage d'expiration — celle qui supprime les comptes non exemptés — lit des colonnes (csowner, csadmin, csop, csvoice), jamais des noms de grades. Un renommage de vocabulaire ne l'atteint pas. ⚠ Ce qui impose en retour : ne pas renommer les colonnes cs*.
⚠ Le carnet disait « cinq vocabulaires ». Il y en a six — celui du panel manquait, et c'est le seul que l'exploitant voie en se connectant. Il disait aussi « coadmin n'existe dans rien de ce qui décide » : vrai du Tcl du bot (zéro occurrence), faux du panel, où LEVEL_COADMIN = 60 décide de ce qu'on peut ouvrir.
⚠⚠ CE POINT S'EST PÉRIMÉ EN UN JOUR, ET IL A FALLU LE MESURER POUR S'EN APERCEVOIR. Il annonçait « la table attend une décision » et « trois lignes vides ». Mesuré le 14/08/2026 : la table était tranchée depuis la veille, il n'y restait qu'une ligne ouverte, et deux des quatre étapes du §5 étaient déjà faites.
L'état réel, au 14/08/2026 :
| Étape du §5 | État mesuré |
|---|---|
| 1. Le panel | ✅ fait en 4.16.61 — auth.php:59-66 rend Net-admin · Admin · Chef-de-bot · Opérateur · Utilisateur |
| 2. Les libellés de salon | ✅ fait en 4.16.62 — CHANACCESS_LIBELLES porte halfop => 'Helpeur' |
| 3. Les descriptions | ✅ retirée du plan le 13/08 — personne ne lit cette colonne |
4. require_grade | ✅ écrit le 14/08 (e24288b) — ⚠ pas encore en service, voir plus bas |
✅ Le dernier trou de la table était comblé dans les faits. GRADES.md §4 disait d'owner (5) qu'il « reste à nommer » ; or Web/inc/chanaccess.php:77 rend déjà « Propriétaire », et la base décrit Propriétaire du salon. Confirmé par SerVuS le 14/08 — aucun travail technique n'en découle. Les deux affirmations périmées de GRADES.md ont été corrigées le même jour.
⚠ Deux fausses alertes écartées par la mesure, écrites pour ne pas être refaites :
- l'inversion
owner/adminentre les deux échelles (owner(5) < admin(6)en base,admin < ownerdansrequire_grade) n'est pas un défaut :GRADES.md§1 la documente — « les deux sont justes, sur deux échelles ». Elle ne paraît suspecte qu'au §4 lu seul ; ::soc::xdatane lit pas le userfile malgré son nom, qui évoque lextrad'Eggdrop : il fait des requêtes SQL sur${t}_users. Le userfile ne porte quecreatedcomme champ XTRA, vérifié. Aucune migration à froid n'est nécessaire.
✅ L'ÉTAPE 4 EST ÉCRITE — 14/08/2026, commit e24288b. Trois clés renommées, sans accent ni tiret (tranché par SerVuS : ce sont des clés comparées par lsearch, jamais affichées, et une clé accentuée mal saisie donnerait un grade inconnu — que require_grade refuse par défaut) :
| Clé | Devient | Appels |
|---|---|---|
op | operateur | 12 |
owner | proprietaire | 4 |
ircops | netadmin | 3 |
admin, user, voice | inchangés | 45 épargnés |
⚠ Mon estimation de périmètre, écrite ici même, était trop large : j'annonçais que les 8 cas du switch de ::soc::xdata devaient bouger. Faux. get_grade traduit déjà — les cas s'appellent isop, isvoice, isauth, déjà différents des grades rendus. Seules les valeurs de retour changent. Le vrai périmètre : la liste, six return, dix-neuf appels — tous littéraux, aucun via variable, donc tous repérables.
⚠⚠⚠ UN TROISIÈME DÉBORDEMENT, ET IL EST RESTÉ ROUGE SANS QUE PERSONNE LE VOIE — 14/08/2026, 18:20. Trouvé en passant le lanceur complet : test-commandes.php était le seul échec sur 69 bancs, et il l'était déjà avant la séance. Mesuré sur la prod pour ne pas s'accuser à tort : même rouge, même constat.
Le renommage de e24288b est à mi-chemin dans Web/inc/commandes.php — les commandes ont changé de grade, la table qui les libelle non :
| Grade employé | Commandes | Déclaré par cmd_rangs() ? |
|---|---|---|
ircops | 65 | oui — ancien nom, le Tcl rend netadmin |
admin | 32 | oui — inchangé, rien à faire |
public | 28 | oui |
operateur | 12 | ❌ non — nouveau nom, la page ne sait pas le libeller |
user | 5 | oui |
proprietaire | 3 | ❌ non — idem |
voice | 2 | oui |
owner | 1 | oui — ancien nom, le Tcl rend proprietaire |
op | 0 | oui — déclaré et plus employé, reliquat |
Ce que rend le Tcl, dérivé de la source : netadmin · proprietaire · admin · operateur · voice · user. Trois noms de la page n'y sont plus (ircops, owner, op), et deux noms du Tcl manquent à la page (operateur, proprietaire).
⚠⚠⚠ ET « 66 COMMANDES À RENOMMER » ÉTAIT FAUX — l'erreur était dans ce carnet, pas dans le code. SerVuS a demandé le renommage sur la foi du tableau ci-dessus. Mesuré avant de le faire, le 14/08 à 18:40 : il n'y a rien à renommer dans les commandes. Le seul défaut était que cmd_rangs() ne déclarait pas operateur ni proprietaire.
Ce que la mesure a montré, en lisant les gardes réelles plutôt que les noms :
| Ce qui garde la commande | Combien | Le rang qui lui revient |
|---|---|---|
::soc::require_grade | 60 — admin 40, operateur 12, user 3, proprietaire 3, voice 2 | le grade comparé, dans l'échelle {user voice operateur admin proprietaire netadmin} |
require_ircop · require_admin · botisircops | 57 occurrences | ircops — ces gardes interrogent le SERVEUR, jamais la table des grades |
matchattr $hand n | 1 (syncowner) | owner — l'owner Eggdrop, sans rapport avec csowner |
⚠ Zéro require_grade … netadmin dans tout le projet — les trois occurrences trouvées sont des commentaires, et j'ai failli les compter (le grep initial ne les excluait pas). Le grade netadmin existe dans l'échelle et n'est exigé nulle part.
ircops n'est donc pas un ancien nom de netadmin : c'est le nom d'une autre autorité. Le renommer aurait fait rougir test-grades.php, qui dérive ircops de ces gardes-là — il aurait fallu truquer le banc pour faire passer le renommage. ⚠ Le nom suit l'autorité qui décide, pas l'inverse.
✅ DÉCISION DE SERVUS, 14/08/2026 : « laisse ircops comme ça ». Le choix inverse lui a été proposé avec son coût — vocabulaire uniforme, mais banc et règlement public à reprendre. La question est fermée, et ce paragraphe existe pour qu'on ne la rouvre pas à la prochaine lecture du mot « ircops ».
✅ Corrigé le 14/08/2026 : cmd_rangs() déclare désormais operateur (3) et proprietaire (5), op a disparu — plus aucune commande ne le portait —, et les deux clés qui ne sont pas des grades SQL disent maintenant ce qu'elles sont : ircops « vérifié auprès du serveur, jamais par un grade en base », owner (Eggdrop) « flag n du userfile, sans rapport avec la colonne csowner ». Leur description mentait plus discrètement que le rouge.
⚠ La mutation qui vaut plus que le reste : passer syncowner de owner à proprietaire fait rougir test-grades.php. Le renommage aveugle des 66 aurait donc été arrêté par un banc — mais seulement après l'avoir écrit. Mesurer les gardes coûte dix minutes, défaire un renommage en coûte davantage.
69 bancs au vert, 3874 constats — le rouge qui traînait depuis 15:41 est parti.
⚠ Pourquoi test-grades.php reste vert malgré cela : il croise la documentation affichée avec ce que le code exige, et ces 15 commandes sont cohérentes de ce point de vue. C'est test-commandes.php qui croise le grade employé avec la table des libellés. Deux bancs, deux questions — et seul le second voit ce trou. Un banc vert ne couvre que sa propre question.
⚠⚠ test-grades.php A ATTRAPÉ DEUX DÉBORDEMENTS, et c'est pour cela qu'il existe. Le premier : il croise la documentation affichée sur le site avec ce que le code exige — le renommage du seul Tcl a désaligné 23 commandes. Web/inc/commandes.php a suivi. Le second, plus fin : syncowner annonçait « owner » et je l'avais renommé avec les autres ; sa propre table le dit pourtant — « matchattr $hand n : owner EGGDROP, pas un grade SQL ». Deux mots identiques, deux autorités différentes. Remis en place.
Éprouvé : contrôle croisé (tout ce que get_grade rend existe dans order) · banc dédié à 11 cas dont les trois gardes contre l'erreur · mutation attrapée — remettre op dans order pendant que get_grade rend operateur refuse un opérateur partout · les 15 bancs Tcl verts · test-grades.php 29/0, test-chanaccess 48/0, test-colonnes 7/0, test-panel-account 15/0.
✅ LE REDÉMARRAGE QUE CE POINT ATTENDAIT A EU LIEU — 14/08/2026 à 17:13:14. Le commit e24288b datant de 15:41, les nouvelles clés sont en mémoire depuis. La condition est donc remplie : il ne manque plus que l'essai. ⚠ Et le symptôme annoncé plus bas ne s'est pas produit — require_grade refuse par défaut, donc un désaccord aurait fermé toutes les commandes d'un coup ; le service tourne depuis, sans plainte.
✅ LE ROUGE QUI TRAÎNAIT EST FERMÉ — 14/08/2026, 18:45. cmd_rangs() déclare operateur et proprietaire, op est retiré. ⚠ Aucune commande n'a été renommée : la mesure a montré que les 65 ircops et le owner de syncowner répondent à d'autres autorités que require_grade — voir plus haut. Six mutations attrapées, 69 bancs au vert.
✅✅ L'ESSAI EN SERVICE EST FAIT — 14/08/2026 à 18:43, ET DANS LES DEUX SENS. Une seule connexion, ClaudeOps en TLS sur 6697, sur #accueil :
sans auth → IriX NOTICE ClaudeOps :Accès refusé.
avec auth → IriX MODE #accueil +o ClaudeOps
Journal du bot : 18:43:37 [INFO] op ClaudeOps/#accueil par ClaudeOps, et ${t}_events porte le MODE +o et la trace mode +o ClaudeOps par ChanAdmin.
⚠ Le premier sens compte autant que le second. Un !op qui marche ne prouve pas que la garde tient — seulement qu'elle n'a pas mordu. C'est le refus sans auth qui montre que require_grade … operateur lit bien le grade, et le succès avec qu'il reconnaît le nouveau nom. auth a été remis à non aussitôt, et la connexion fermée.
⚠ CE QUI N'EST PAS ÉPROUVÉ, ET POURQUOI ON NE VA PAS L'ÉPROUVER : la moitié admin. ClaudeOps porte csop et rien d'autre, par une décision prise le 10/08 — « un accès de vérification n'a pas à pouvoir tout faire ». Lui donner csadmin pour un essai reviendrait à défaire cette décision pour la commodité d'une mesure. Ce qu'on sait à la place : admin est bien dans order ({user voice operateur admin proprietaire netadmin}), et le symptôme qu'annonçait ce point — require_grade refusant par défaut, donc toutes les commandes fermées d'un coup — ne s'est pas produit depuis le redémarrage de 18:30:19.
| Pour fermer | ✅ FERMÉ le 14/08/2026. Le renommage est en service et constaté dans les deux sens sur operateur. ⚠ Et une commande admin doit continuer de marcher : 40 appels ne changent pas, mais c'est la liste entière qui a été réécrite. ⚠ Si quelque chose casse, le symptôme sera net et global — require_grade refuse par défaut, donc toutes les commandes se fermeraient d'un coup, pas une seule. ⚠ Ne PAS renommer les colonnes cs* ni ajouter un nom à la seule liste : il deviendrait un niveau fantôme qui décale les rangs. ⚠⚠ Et ne PAS renommer ircops ni le owner de syncowner « par cohérence de vocabulaire » : ils ne relèvent pas de require_grade, et test-grades.php rougit si on le fait. ✅ Tranché par SerVuS le 14/08/2026 : « laisse ircops comme ça » — la question a été posée avec son coût (le banc et le règlement public à reprendre), elle est fermée. |
|---|
9. 🔨 Les quatre éditions — OUVERT le 14/08/2026, phases 1 et 2 FAITES, la 3 OUVERTE le 01/09 (écrivain posé, pas encore constaté sur machine nue)
Le 17/08/2026 — la phase 2 commence, et le plan mentait sur sept points
✅ Les quatre éditions s'exécutent enfin, dans les trois langages.
Web/tests/test-edition.phppasse de 19 à 43 constats. Avant ce jour,beta,standardetpayanten'avaient jamais tourné nulle part : les trois lecteurs ne voyaient quecomplete, et le bac à sable du banc n'éprouvait les cas limites que dans un des trois langages — trou qu'il avouait lui-même dans son en-tête. Chaque lecteur est forcé par son propre point d'injection (ROOT,::soc::script_dir,_edition_racine), sans jamais toucher à la configuration réelle.⚠⚠ CE QUI RESTAIT DE LA PHASE 2 — REMPLI le 25/08/2026 (le texte d'origine est gardé : il dit la difficulté, et elle était réelle) : « les quatre campagnes passent » ne prouvera rien tant qu'aucune ligne ne se comporte différemment selon l'édition — tous les bancs rendraient le même résultat sous les quatre. ⚠ Le compte qui figurait ici — « 84 » — a été retiré le 24/08/2026 : il en annonçait 84 pour plus de cent.
./Tools/bancs.sh --listeles découvre par glob. Un banc qui ne peut pas rougir ne garde rien. Il faut donc une première différence de comportement, petite et sûre, avec le banc qui l'éprouve sous les quatre. C'est elle qui fera d'edition_au_moinsautre chose qu'une fonction jamais appelée.⚠⚠ ET
Markdown/EDITIONS.mdDÉCRIVAIT LE MONDE DU 11/08 — sept écarts, corrigés le 17/08. Le document qui pilote ce chantier affirmait :
Ce qu'il disait Ce qui est le champ editionn'existe pas (trois fois)posé le 14/08 la phase 1 est à faire faite le 14/08 la sauvegarde n'a jamais produit d'archive close le 11/08 à 18 h 57, le jour même la phase 0 est bloquante, le point 2 ouvert point 2 clos le 15/08, phase 0 remplie quatre autres points bloquants tous clos la question 5.5 est « encore ouverte » ✅ tranchée le 14/08 — contradiction avec son propre §5.5 la phase 5 « part de » la fonction sans_secret()⚠⚠ elle n'existe nulle part dans le dépôt ⚠ Le dernier est le plus grave, et d'une autre nature : une date périmée se corrige en mesurant ; une fonction fantôme fait bâtir sur du vide, et faisait passer la phase 5 pour « largement livrée » alors que sa pièce maîtresse — le filtre qui retire les adresses et les secrets d'un rapport avant envoi — reste entièrement à écrire.
📌 Cette dernière ligne décrit le 17/08 et RIEN D'AUTRE — ne pas la relire comme du travail à faire. Le filtre a été écrit le 27/08/2026 (
Web/inc/doctrine-rapport.php), et les deux chemins d'envoi qui lui échappaient encore ont été fermés le 28/08. Voir plus bas, « Phase 5 — les DEUX chemins d'envoi hors du filet sont fermés ».⚠ La leçon vaut plus que les corrections : un plan qui se trompe sur son propre état ne se suit pas, il s'invente.
CHANTIERS.mds'applique déjà la règle de mesurer chaque point au moment où l'on travaille dessus ;EDITIONS.mdne se l'appliquait pas, et six jours ont suffi.
⚠⚠ OUVERT SUR DÉCISION DE SERVUS, contre l'avis écrit ici même. Ce point disait « à ne pas démarrer maintenant » ; la réserve lui a été présentée avec son coût, et il a répondu « tout le chantier des éditions ». C'est sa décision, et elle est prise en connaissance de cause — le paragraphe d'origine est gardé plus bas, il dit pourquoi c'était risqué.
✅ La question 5.5, la dernière ouverte, est tranchée : dix installations au plus. Elle ferme le §6 plus qu'elle ne l'ouvre — la licence MIT impose de vendre le service (voie 1), il n'y a rien à payer pour distribuer, et un serveur de licences est sans objet à cette échelle. ⚠ Un seul point lourd survit : héberger les réseaux des clients est un autre métier, à dix comme à mille.
✅ PHASE 1 FAITE — le socle, et personne qui devine.
| Pièce | Où |
|---|---|
| le champ | Config/config.json → project.edition = complete |
| lecteur PHP | edition_lire() · Web/inc/edition.php |
| lecteur Tcl | ::soc::edition · IriX/cmds/irix-edition.tcl |
| lecteur bash | edition_lire() · Tools/edition.sh |
| le croisement | Web/tests/test-edition.php — ⚠ le compte n'est pas recopié ici, il se lit en le lançant |
⚠ Le champ vit dans config.json pour une raison mesurée : ce fichier est déjà exclu de la surveillance de mise à jour (_upd_exclu), donc l'amont ne l'écrase jamais. C'est un fichier d'état propre à l'installation — exactement ce qu'une édition est.
⚠ Le défaut prudent FERME : champ absent, illisible ou inconnu ⇒ beta, la plus restreinte. Jamais la plus permissive. Et edition_au_moins compare des rangs, jamais des égalités — une édition demandée inconnue rend faux, comme require_grade refuse sur un grade inconnu.
⚠⚠ QUATRE PASSES DE MUTATION, ET DEUX COMMENTAIRES FAUX ÉCRITS EN CHEMIN. Le motif qui lit le champ doit éviter la note _edition, qui cite « beta » et « payante ». J'ai d'abord écrit que les guillemets protégeaient, puis — après une mutation restée verte — que c'était la forme [a-z]*. Les deux sont vrais, et chacun suffit seul : il faut casser les deux gardes ensemble pour que la note soit lue, ce que la quatrième passe a fini par montrer. ⚠ Une mutation qui ne rougit pas ne prouve pas que le code est solide — elle peut aussi dire que la mutation ne mord pas, ou que le cas de test compare deux valeurs identiques. Les deux se sont produits ici.
⚠ Ce que le banc n'attrape pas, et c'est écrit dans son en-tête : son bac à sable n'éprouve que le lecteur bash. Casser les deux gardes du motif Tcl le laisse vert. Le croisement des trois lecteurs limite les dégâts, mais seulement sur la configuration réelle.
✅ LE MODULE TCL EST CHARGÉ — ce paragraphe décrivait un défaut CLOS depuis le 15/08/2026 à 19:06:20 (point 18), et il l'a décrit au présent pendant deux jours. Corrigé le 16/08. Mesuré : IriX-sql.tcl porte bien son source, ::soc::start_system journalise l'édition avec une branche WARN de repli, et test-modules-charges.tcl dérive désormais la liste des source. ⚠ Le renvoi « l. 586-620 » qu'il portait ne désignait déjà plus rien — un numéro de ligne recopié dans une prose. [[renvois-numero-de-ligne]]
⚠⚠ CE QUI ÉTAIT VRAIMENT OUVERT ÉTAIT AILLEURS — constaté le 16/08/2026, REMPLI le 25/08/2026. La phase 1 avait posé une vérité que personne n'interrogeait pour décider : les trois lecteurs n'avaient qu'un appelant d'affichage chacun, et edition_au_moins — la seule fonction capable de conditionner quoi que ce soit — zéro appelant dans les trois langages. Aucune ligne du dépôt ne se comportait différemment selon l'édition. La phase 1 était finie à la lettre de sa condition, et pas en esprit.
✅ PHASE 2 FAITE — la première différence de comportement existe, et elle est en production.
| Lecteur | Appelants en production, mesurés le 25/08/2026 |
|---|---|
Web/inc/edition.php | Web/inc/compat.php pour décider, plus dashboard.php et compatibilite.php pour afficher |
IriX/cmds/irix-edition.tcl | un seul — la ligne de journal écrite pour prouver que le module est chargé |
Tools/edition.sh | ⚠ AUCUN, et c'est INCHANGÉ — l'installateur ne le source toujours pas |
compat_serveurs_edition() appelle edition_au_moins(), et la page des serveurs compatibles n'ouvre pas les mêmes serveurs selon l'édition :
| beta | standard | complete | payante |
|---|---|---|---|
| 1 serveur | 2 | 6 | 6 |
⚠ Ce qu'une édition ferme, la page le DIT : compat_serveurs_fermes() nomme chaque serveur retiré avec l'édition qui l'ouvrirait. Une fonction éteinte se dit, elle ne se cache pas — un serveur qui disparaît sans un mot laisse croire à un oubli ou à une incompatibilité, deux choses fausses. ⚠ Et la production ne change pas : elle vit en complete, et voit les six comme avant.
⚠ Le banc qui l'éprouve sous les quatre éditions : Web/tests/test-edition.php. ⚠⚠ Cette ligne annonçait AUSSI Web/tests/test-compat.php, et c'était FAUX — mesuré le 31/08 : ce banc ne contient pas une seule occurrence du mot « édition » sur ses 302 lignes. EDITIONS.md avait corrigé le 30/08 ; la correction n'était appliquée que dans un des deux documents, et l'autre a continué d'affirmer la même chose un jour de plus. Leur compte n'est pas recopié ici — il se lit en les lançant, et ./Tools/bancs.sh les ramasse.
⚠⚠ MAIS LE LECTEUR BASH RESTE UN LECTEUR MORT, exactement au sens du point 18 — le seul code qui l'appelle est son propre banc, ce que le point 18 a nommé « le chemin aveugle ». C'est précisément le piège de la phase 3, décrit plus bas.
| Ce qui reste au chantier | Les phases 3 à 8 : l'installateur par mode, la montée d'édition, le signalement gradué (largement livré avec l'alerte), le site par édition, Windows, l'édition payante. ⚠ La campagne de bancs par édition — le gros du travail, et elle venait AVANT tout le reste — est faite avec la phase 2. |
|---|
⚠⚠ DEUX CONDITIONS DE BLOCAGE ÉCRITES ICI SONT PÉRIMÉES — corrigé le 16/08/2026 :
- « La Phase 0 n'est toujours pas remplie — le 2 ne l'est pas » : faux. Le point 2 est clos depuis le 15/08 à 21:38:37, et les points 1 à 8 portent tous ✅. La condition littérale est remplie.
- « pour combien d'installations ? », donné comme le blocage principal : tranché le 14/08/2026 — « dix au plus », écrit dans
Markdown/EDITIONS.md§5.5, qui conclut « plus rien ne bloque l'écriture ». ⚠ Et ce chiffre ne conditionne que la phase 8 (l'édition payante) : le même document dit qu'il « n'allège que le §6, la partie technique reste entière ».
⚠⚠ MAIS LA RAISON DE NE PAS OUVRIR TIENT TOUJOURS, et elle est désormais MESURÉE plutôt que prédite. L'argument d'origine disait : « quatre éditions, ce sont quatre choses à faire tourner, et trois que personne ne fait tourner tous les jours ». Ce n'est plus une prédiction : sur l'installation réelle, les trois lecteurs ne voient jamais que complete, aucune autre édition n'existe dans aucune exécution, et edition_au_moins n'a jamais été appelée. La fermeture des points 1 à 8 a levé le blocage listé, pas sa raison.
⚠ La phase à ne surtout pas ouvrir en premier est la 3 (l'installateur par mode) : elle brancherait un lecteur mort dans le plus gros fichier du dépôt (12 509 lignes, remesuré le 30/08/2026 — la valeur écrite ici annonçait 10 429, et elle avait pourtant été présentée comme mesurée) et y ajouterait quatre chemins d'installation dont trois ne seraient jamais parcourus.
✅ L'argument « lecteur mort » a été RETIRÉ pour bash le 30/08/2026.
Tools/tests/test-edition-sh.shexerceedition_au_moins()— quatorze comparaisons de rang, cinq mutations attrapées. La fonction n'avait aucun appelant, ni en production ni dans un banc : les seules lignes du dépôt qui la nommaient la livraient sans jamais l'exécuter. ⚠ Cela n'ouvre PAS la phase 3 : les quatre chemins d'installation restent à écrire, et leur condition de fin — « les quatre modes s'installent sur une machine nue » — demande une machine nue, root, et quatre installations réelles. C'est un geste, pas du code. ✅ Et pour le Tcl le 31/08/2026 —IriX/tests/test-edition.tcl, 27 constats. Les trois lecteurs ont désormais chacun leur banc. ⚠ Cela ne rend pas les lecteurs vivants :::soc::edition_au_moinsetedition_au_moins()en bash n'ont toujours aucun appelant en production. Sur les trois, seul le PHP en a un. Ils sont mesurés, pas employés — et c'est la moitié « jamais exécutée » qui a été retirée, pas l'autre. 📌 Les deux constats qui portent ce banc : une édition demandée inconnue refuse, mesurée sur l'installation la plus complète — c'est le défaut derequire_grade, où unlsearchrendant -1 comparé par>=ouvre au lieu de refuser, et mesuré ailleurs le constat serait vrai sans rien prouver ; et les deux gardes contre les notes_edition/_bots_chan, qui citent en toutes lettres les valeurs cherchées.
🔨 Phase 3 — L'ÉCRIVAIN EST POSÉ le 01/09/2026, la phase n'est PAS close
⚠ Elle ne se ferme pas ici. Sa définition d'achèvement est « les quatre modes s'installent sur une machine nue » — cela n'a pas été fait. Ce qui suit décrit ce qui EXISTE, pas ce qui est constaté en service.
⚠⚠ ET LA MESURE A CORRIGÉ LE DIAGNOSTIC — dans le sens le plus dur. Le 31/08 j'ai écrit que « toute installation naît complète, et rien ne peut l'en faire sortir ». C'est vrai du clone git, c'est-à-dire de la production. Mesuré le 01/09 sur l'archive réellement servie :
Config/config.json dans /dl/site → ABSENT (0 entrée)
Une installation tierce ne recevait donc jamais ce fichier, et rien ne le créait. Les trois lecteurs rendaient beta par défaut prudent — non par choix, mais faute de fichier. Le mécanisme n'avait pas « un seul état atteignable » : il en avait un seul par accident de fourniture, et l'édition ne pouvait pas être posée du tout.
📌 Un diagnostic tiré du dépôt décrit le dépôt. L'artefact servi est une autre chose, et c'est lui que reçoivent les autres.
Ce qui est posé, et mesuré :
| Où | Quoi |
|---|---|
Tools/edition.sh | edition_ecrire pose sans juger ; edition_monter refuse toute descente puis l'appelle. L'écrivain vit dans le fichier qui porte déjà edition_connues — ailleurs, la liste des noms valides existerait à deux endroits |
Config/config.json.example | dérivé du fichier réel, jamais recopié ; inscrit dans DISTRIB_RACINE_SITE, la liste qui sert deux fois — un fichier ajouté au seul tar serait emporté sans jamais être surveillé |
Bootstrap/overtchat-setup.sh | --edition=NOM, son aide, et _edition_poser qui source le lecteur au lieu d'en écrire un second. C'est aussi le premier appelant de ce lecteur, qui n'en avait aucun depuis le 14/08 |
⚠⚠ Trois cas, et le troisième est le plus important : option donnée → on pose ; rien et champ absent → défaut prudent ; rien et champ présent → on ne touche à rien. Relancer l'installateur ne redescend pas une installation qui tourne — ce serait éteindre en silence ce qui marchait.
Bancs : Tools/tests/test-edition-sh.sh (53 constats, 8/9 blessures mordantes — la neuvième est déclarée inatteignable dans le banc plutôt que couverte par un constat qui passerait pour une autre raison) et Bootstrap/tests/test-edition-installateur.sh (16 constats, 9/9).
📌 Ces derniers EXTRAIENT _edition_poser du script et la FONT TOURNER sur un bac à sable, avec ses quatre cas. C'est la leçon du 31/08 appliquée à sa propre correction : un banc qui mesure la chose ne mesure pas son emploi.
⚠ Deux gardes de non-descente se masquent l'une l'autre. La mutation l'a montré : blesser l'une ne fait rougir aucun constat, l'autre rattrape. Chacune paraît morte examinée seule — et c'est exactement le raisonnement qui ferait supprimer la survivante. C'est écrit dans le code.
⛔ Ce qui reste pour fermer la phase 3 :
- Installer sur une machine nue, une fois par édition — l'installation d'essai sert à cela ;
- constater que
Config/config.jsony arrive par l'archive, puis que--edition=s'y pose ; - et que la campagne de l'édition obtenue passe.
⚠ Rien de tout cela n'est fait. La phase 4 (la montée) reste bloquée derrière.
Juste derrière la phase 3 vient la 6, qui répandrait en 86 endroits une fonction qui n'a jamais tourné — le mode d'échec exact du point 22, où cinq constats restaient verts pendant une panne totale.
✅ Phase 5 — les DEUX chemins d'envoi hors du filet sont fermés, le 28/08/2026
Le 27/08, la doctrine du rapport (Web/inc/doctrine-rapport.php) était écrite mais deux chemins lui échappaient, nommés au banc sans être traités : Bootstrap/overtchat-setup.sh (bash, sendmail -t) et Components/Service/lib/Tcl/Stable/IriX-std.tcl (mutt, dans la bibliothèque distribuée). Les deux portent désormais la doctrine et l'appliquent.
⚠ Les nommer sans les traiter est ce qui a permis de les fermer le lendemain sans les redécouvrir. Un défaut connu et écrit coûte une journée ; un défaut oublié coûte l'accident.
⚠⚠ CE QUE CELA CRÉE, ET C'EST LE VRAI SUJET : la doctrine est écrite TROIS fois. Ni bash ni Tcl ne peuvent appeler le PHP — l'alerte de l'installateur part précisément quand l'installation va mal, et IriX-std.tcl tourne dans un Eggdrop. Or un calcul écrit des deux côtés diverge un jour est une constante mesurée de ce projet.
La parade est Web/tests/corpus-rapport.txt, et il n'y en a pas d'autre. Un seul fichier de cas, lu par les trois bancs — celui de PHP, celui de bash, et le chapitre ajouté à IriX/tests/test-std.tcl. Le jour où une implémentation dérive, son banc rougit sur la ligne exacte. ⚠ Toute correction apportée à l'une des trois commence par une ligne ajoutée au corpus : un cas absent n'est éprouvé nulle part.
⚠⚠ LE CORPUS A PAYÉ DEUX FOIS LE JOUR DE SON ÉCRITURE.
| Ce qu'il a attrapé | Ce que c'était |
|---|---|
| un défaut du PHP, vieux d'un jour | --PASS2 était nommé dans le motif et pourtant inatteignable : \b exige un caractère de mot avant le premier tiret, et il n'y en a jamais. Mesuré sur la forme réelle du fichier de robots — - b OverOp-T --PASS2 deuxmots — le filtre retirait zéro élément. C'est le secret de lien botnet, en clair dans IriX.user |
| la première écriture bash | l'étape IPv6 comptait deux deux-points au lieu de trois : elle caviardait donc tous les horodatages, 14:00:00 en ayant deux |
⚠ Le premier est le plus instructif : un motif qui cite un cas sans l'attraper ment mieux qu'un motif qui l'ignore. Le lecteur avait toutes les raisons de croire le cas couvert.
⚠⚠ ET LA PASSE DE MUTATION A TROUVÉ UN CINQUIÈME CONSTAT FAUX, dans un banc écrit le jour même. Le chapitre Tcl « personne ne contourne le filet » rejouait le filtre lui-même sur la matière, puis constatait que le filtre filtre. Le corps de send_mail débranché — set body $body — il restait vert, 65 constats, 0 échec. Il prouvait que la fonction existe, jamais qu'elle sert. Il intercepte désormais ce que mutt reçoit réellement, par le fichier passé derrière le chevron. C'est le travers n°1 du projet, et il se reforme jusque dans le banc écrit pour le garder.
⚠ Par quel mécanisme cela voyage-t-il ? — la question qui a manqué cinq fois, posée et mesurée : overtchat-setup.sh est la première entrée de son propre CORE_WATCHED, donc il se met à jour lui-même ; IriX-std.tcl est écrasé par _irix_sync_source, sans aucune garde « seulement si absent » — le défaut d'install-service.sh n'est pas reproduit ici. Il n'est déployé dans cmds/ qu'en mode std, et c'est juste : c'est le seul mode où ce fichier est chargé, donc le seul où irix:send_mail existe.
✅ Phase 5 — le CODE est complet, le 28/08/2026
Les deux derniers morceaux — la table des niveaux et le plafond des six heures — cachaient chacun un défaut mesurable, là où l'on attendait du travail de documentation.
Le plafond était GLOBAL, et son commentaire mentait sur son propre code. Il disait « 6 h entre deux alertes identiques » ; sujet_court n'entrait nulle part dans la décision. Une mise à jour qui échoue à 03:00 sur un dépôt injoignable faisait taire six heures l'alerte de 04:00 disant que la base a disparu. Il compte désormais par clé, comme irix-alerte.tcl depuis le 16/08.
Et la ligne la plus grave du journal s'affichait EN VERT. ::soc::alerte::lever écrit sa trace par un toupper, ce qui fabrique CRIT — un mot qui n'appartient à aucune des cinq échelles de gravité du projet. La coloration de Web/pages/logs.php ne connaissait que ERROR et WARN ; la ligne tombait donc sur la règle suivante, « contient SOC → vert », posée par la ligne même qui l'avait écrite. Une affirmation qui ment dans le sens rassurant — la forme la plus pure du défaut n°1.
Web/inc/niveaux.php porte la correspondance à un seul endroit, et Web/tests/test-niveaux.php dérive les cinq listes de leurs sources pour exiger qu'elle les connaisse toutes. ⚠ Au passage, EDITIONS.md en oubliait un sixième côté C : LOG_FATAL.
⚠ Ce qui reste, et c'est du GESTE : provoquer un incident pour voir partir le bon niveau — cela envoie un vrai courriel, donc cela revient à SerVuS. Le silence par clé n'a jamais été vu à l'œuvre sur une vraie mise à jour en échec.
Le texte d'origine, gardé — il dit pourquoi c'était risqué
Demandé par SerVuS le 11/08/2026, explicitement « pour quand tout sera stable ». Découper le projet en quatre : Beta (découverte), Standard (novice), Complète, Payante. Plus deux chantiers greffés : le budget et l'hébergement de l'édition payante, et une compatibilité Windows avec un installateur qui règle tout selon le mode choisi.
📄 Tout est dans Markdown/EDITIONS.md — l'inventaire mesuré du projet, les quatre éditions détaillées, cinq questions à trancher avant d'écrire une ligne, la licence, l'hébergement et le portage Windows.
⚠⚠ CE POINT NE DOIT PAS S'OUVRIR TANT QUE LES AUTRES SONT LÀ. Un projet qu'on découpe en quatre multiplie par quatre ce qu'il a de bancal. Les défauts du 11/08 — un compteur faux à trois endroits, un SQUIT qui emportait le mauvais lien, un panel annonçant « non déclaré » pour des services en ligne — ne se sont vus qu'en faisant tourner. Quatre éditions, ce sont quatre choses à faire tourner, et trois que personne ne fait tourner tous les jours.
⚠ Le point le plus dur n'est pas technique : le projet est sous licence MIT, et n'importe qui peut donc retirer une limitation et redistribuer. Une édition payante fondée sur du MIT vend un service, pas un logiciel — ou demande de changer de licence. EDITIONS.md §6.1.
| Pour fermer | Ce n'est pas un défaut à corriger : c'est un chantier ouvert le 14/08/2026 sur décision de SerVuS. Il se ferme quand les quatre éditions existent, chacune avec sa campagne de bancs — sans quoi trois sur quatre casseront sans que personne le voie. ⚠ La condition d'entrée — les cinq questions du §5 — est remplie : les quatre premières l'étaient au 14/08, la 5.5 a été tranchée le même jour (« dix installations au plus »). Elle est gardée ici parce qu'elle dit ce que chaque phase doit continuer de respecter, pas parce qu'elle bloque encore. |
|---|
10. Les pseudonymes du réseau — la moitié qui demande un mot de passe
Mesuré le 12/08/2026, et le défaut n'était pas celui qu'on croyait. La note de reprise disait « les pseudonymes des robots et services ne sont protégés par rien, pseudos-proteges.conf compte zéro entrée ». C'était vrai, et incomplet : l'autre liste, celle des interdits, comptait bien 80 motifs — dont un qui paraissait couvrir les services et n'en couvrait aucun.
over-* exige un tiret après over. Les services s'appellent OverBot, OverHub, OverNick, OverWeb. Le croisement des motifs avec les pseudonymes réellement en ligne a donné neuf à découvert sur dix — le seul gardé l'étant par une autre ligne.
⚠ Il ne s'est pas vu en relisant le fichier, où la ligne se lit très bien, mais en croisant deux sources : les motifs livrés, et ${t}_online. C'est le même geste que pour les compteurs de charge du point 2, et il trouve le même genre de défaut — une affirmation qui a l'air vraie et ne l'est pas.
✅ Réglé pour les pseudo-clients du lien de services (4.16.49) : ils sont nommés un par un, et une campagne du serveur (test-nickpolicy.c, 7/17) garde désormais le fichier livré. Elle tient les deux sens — services réservés, robots jamais — et deux mutations la font rougir.
✅✅ ET C'EST CONSTATÉ EN SERVICE, le 12/08/2026 à 14:26 — pas seulement écrit. Report en production, rechargement à chaud sans changement de processus (donc aucune connexion rompue), 84 motifs annoncés au lieu de 80, et les trois cas éprouvés par de vraies connexions :
OverBot → 432 :Erroneous Nickname: Ce pseudonyme est reserve aux services du reseau
OverOp-Q → 433 (deja pris), et non 432 — le robot garde son nom
Overlord → accepte
⚠ Les trois comptent, et pas seulement le premier. Sans le troisième, un refus général se lirait comme un succès ; sans le deuxième, on ne saurait pas qu'un robot peut encore se connecter — c'est le sens qu'un motif élargi aurait cassé en silence. Recoupé par le journal du serveur, source indépendante du client d'essai, qui ne porte qu'une ligne : celle du seul refus attendu.
⚠⚠ Pourquoi les deux sens, et pas seulement le premier. Le correctif évident, élargir en over*, casserait les robots de prêt : ce sont des clients, et cette liste s'applique aux clients. Ils ne tomberaient pas franchement — Eggdrop bascule sur son pseudonyme de repli et continue de tourner, le prêt cassé sans une ligne au journal. Un banc qui n'aurait gardé que « les services sont réservés » aurait laissé passer ce correctif-là.
✅ L'ÉTAT ET LA PROCÉDURE, MESURÉS LE 14/08/2026 — pour que le geste soit court et sûr le jour où il se fera.
Les cinq à protéger : ⚠⚠ CETTE LISTE EST PÉRIMÉE — remesurée le 27/08/2026, et fausse DANS LES DEUX SENS. Le parc a été refait le 25/08 : IriX, OverOp-H, -Q, -S, -T.OverOp-H, -Q et -S n'ont plus ni unité, ni dossier — et OverOp-A, qui n'y figurait pas, tourne et est en ligne. La liste juste aujourd'hui compte trois noms : IriX, OverOp-T, OverOp-A. ⚠ Ne pas la recopier telle quelle : c'est une liste de robots, donc elle bougera encore — la mesurer par systemctl list-units 'overop@*' croisé avec ${t}_online, jamais la lire ici. Les pseudo-clients du lien — OverTchat, OverHub, OverNick, OverBot, OverWeb — sont déjà couverts par les motifs depuis la 4.16.49.
⚠ Deux résidus mesurés au passage : OverOp-H garde un compte - b dans IriX.user sans robot derrière, et OverOp-A tourne sans compte dans IriX.user — c'est le « robot infirme » du point 35, morceau (2).
| Élément | État au 14/08/2026 |
|---|---|
/home/servus/overtchatd/conf/pseudos-proteges.conf | 0 entrée |
| Ses droits | ✅ 600 servus:servus — déjà conformes ; le serveur refuse de charger un fichier plus ouvert et garde la liste précédente |
pseudos-interdits.conf | 84 motifs, en service |
| L'option d'empreinte | ✅ existe : -H <motdepasse>, plus -I <n> pour les itérations, à placer AVANT -H (défaut 100000) |
| Le format d'une ligne | ❌ CETTE LIGNE ÉTAIT FAUSSE — elle annonçait $sha256$sel$empreinte. Mesuré le 16/08 en exécutant -H : pseudonyme:$sha256$<itérations>$<sel>$<empreinte>, QUATRE champs. ⚠ Le format à trois est accepté en lecture mais vaut UNE itération au lieu de 100 000, sans le moindre avertissement : une entrée écrite à la main d'après l'ancienne doc donne une protection cent mille fois moins chère à casser, qui a l'air de marcher. Corrigé le 16/08 dans les 8 endroits du DÉPÔT qui l'annonçaient. Seul include/passwd.h avait raison depuis toujours. ⚠⚠ ET « DONT LE FICHIER INSTALLÉ » ÉTAIT FAUX — mesuré le 27/08/2026, onze jours plus tard : /home/servus/overtchatd/conf/pseudos-proteges.conf et son .example annonçaient TOUJOURS le format à trois champs. La cause n'était pas un oubli : install-service.sh ne posait ces fichiers que s'ils manquaient, donc aucune installation existante — ni la production, ni celle d'un tiers — ne pouvait recevoir une documentation corrigée, jamais. ✅ Le mécanisme est réparé le 27/08 (voir l'encadré au bas de ce point) et les modèles à jour sont posés en production |
| Côté Eggdrop — OverOp | set servers { 127.0.0.1:6667 } — deux champs, aucun mot de passe. Il en faut un troisième : 127.0.0.1:6667:<motdepasse> |
| Côté Eggdrop — IriX | ⚠ PAS DE set servers DU TOUT. irix.conf emploie la forme 1.9+/1.10 server add "localhost" "+6697" : le mot de passe y est un troisième argument, pas un troisième champ de chaîne. ⚠ Et il y a trois branches type_serv — n'en traiter qu'une laisse les deux autres muettes au premier basculement. La recette ci-dessus ne s'applique donc pas à IriX |
⚠ LE FICHIER INSTALLÉ DOCUMENTE UN CHEMIN QUI N'EXISTE PAS LÀ OÙ IL EST LU. Il indique ./build/overtchatd -H. C'est juste dans les sources — build/overtchatd y est bien, et son empreinte est identique au binaire installé — mais à l'endroit où ce fichier vit, le binaire est /home/servus/overtchatd/bin/overtchatd. Vérifié avant d'accuser : les documents du dépôt, eux, ont raison chez eux.
⚠⚠ L'ORDRE COMPTE, et le point ne le disait pas. « Des deux côtés à la fois » est juste, mais le mot de passe part à la connexion, jamais après : Eggdrop ne le renvoie pas à chaud.
- poser le mot de passe dans chaque
overop.conf/irix.conf— inerte tant qu'on ne reconnecte pas ; - poser les empreintes dans
pseudos-proteges.conf; REHASHdu serveur — le fichier est relu à chaud, aucun redémarrage ;- reconnecter les robots un par un, en vérifiant chacun avant le suivant.
⚠ Le symptôme d'une erreur est silencieux CÔTÉ ROBOT : le serveur renvoie 432 ERR_ERRONEUSNICKNAME, Eggdrop bascule sur son altnick — OverOp-H_, -Q_, -S_, -T_, IriX_ — et continue de tourner, le prêt cassé.
❌ « sans une ligne au journal » était FAUX, mesuré le 16/08 : src/cmd/m_register.c écrit Pseudonyme protégé « %s » refusé à %s dans /home/servus/overtchatd/logs/overtchatd.log. La ligne existe — elle est simplement dans le journal du serveur, pas dans celui du robot. Regarder au mauvais endroit revient à ne pas regarder, et le point ne disait pas où.
⚠⚠ LE VRAI DANGER EST DIFFÉRÉ, ET C'EST CE QUI REND L'ÉTAPE 4 INDISPENSABLE. Appliquer un seul côté ne casse rien sur le moment : nickprotect_allows n'est consulté qu'à la prise du pseudonyme, et une session déjà ouverte n'est jamais réinterrogée. Le REHASH de l'étape 3 est donc parfaitement silencieux sur des robots connectés. Qui s'arrête là constate que « tout va bien » — et la panne dort jusqu'au prochain redémarrage non surveillé. Or le service est arrêté chaque soir vers 21:00 pour ne repartir que vers 09:00 : le désaccord se réveillerait le lendemain matin, sans personne devant l'écran.
✅ CORRIGÉ le 16/08 — un défaut qui aurait fait échouer le geste chez tout tiers. contrib/install-service.sh déposait pseudos-proteges.conf en 640, alors que nickprotect_load() refuse tout fichier portant un bit groupe ou autre (« est lisible au-delà de son propriétaire — non chargé, corrigez avec chmod 600 »). Chez un tiers, la protection ne s'armait donc jamais. Invisible ici (la production est en 0600, posée à la main) et invisible là-bas au début, le fichier livré étant vide : « 0 pseudonyme chargé » paraît normal. Cela aurait mordu le jour où l'exploitant y met ses entrées et constate que rien ne s'applique. ⚠ Le mode 600 ne vaut que pour ce fichier : S_IRWXG n'apparaît que dans src/nickprotect.c, les trois autres politiques n'ont aucun contrôle de droits.
⚠ Ce que le verrou « non automatisable » recouvre vraiment. Seule la saisie du secret l'est — et l'argument juste n'est pas « un script le connaîtrait » mais qu'il le connaîtrait des deux côtés à la fois, en clair dans overop.conf et haché pour pseudos-proteges.conf : un unique point de compromission qui réunit les deux moitiés. En revanche écrire les fichiers, faire le REHASH, reconnecter et VÉRIFIER sont entièrement automatisables — rien de secret n'y transite. Ranger les quatre étapes sous un seul verrou prive le geste de sa partie la plus utile : la vérification après chaque robot, qui est précisément celle que l'humain oublie.
✅ UNE CORRECTION NE POUVAIT PAS ATTEINDRE UNE INSTALLATION — trouvé et réparé le 27/08/2026
⚠⚠ Ce défaut-ci n'est pas dans un fichier : il est dans le mécanisme qui livre les fichiers. Il a été trouvé en remesurant ce point, et il vaut bien au-delà de lui.
Ce qui a été mesuré. Le format d'une empreinte a été corrigé dans le dépôt le 16/08/2026 — quatre champs au lieu de trois. Le 27/08, onze jours plus tard, le fichier réellement posé en production annonçait toujours trois champs. Et le format à trois est accepté en lecture : il vaut une itération au lieu de cent mille, sans le moindre avertissement. Un exploitant qui suit son propre fichier obtient donc une protection cent mille fois moins chère à casser, qui a l'air de fonctionner.
La cause, et elle est mécanique. Servers/overtchat-hybrid/contrib/install-service.sh ne posait les quatre fichiers de politique que s'ils manquaient :
if [ ! -f "$PREFIX/conf/${_pol}.conf" ] && [ -f "$SRC_DIR/conf/${_pol}.example" ]; then
Une installation déjà en place gardait donc, pour toujours, la documentation du jour de sa pose. Ni la production, ni celle d'un tiers, ne pouvait recevoir une correction — jamais.
⚠⚠ ET LE BANC EST RESTÉ VERT ONZE JOURS. test-conf-modele.sh portait déjà un constat « aucun document du serveur n'annonce le format à TROIS champs » — mais il balaie Servers/overtchat-hybrid, c'est-à-dire le dépôt, qui était juste depuis le 16/08. Un banc qui ne lit jamais l'installation ne peut pas voir un défaut de livraison.
Ce qui a été fait.
| Le mécanisme | Le modèle (.example) est désormais redéposé à chaque passage, hors de toute condition — il n'est lu par aucun serveur et n'est rempli par personne, le remplacer ne peut rien détruire. La politique (.conf) n'est toujours écrite que si elle manque : les entrées de l'exploitant sont intouchables |
| Et on le DIT | Si le .conf existe et que sa documentation a changé, l'exploitant est averti, en étant renvoyé au modèle frais posé à côté. ⚠ La comparaison porte sur les seules lignes de commentaire : comparer les fichiers entiers crierait dès la première entrée ajoutée, pour toujours — et un avertissement permanent n'est plus un avertissement |
| La forme n'est pas neuve | ⚠ Ce script appliquait déjà exactement cette doctrine à overtchat.conf, vingt lignes plus haut. Elle n'avait simplement jamais été étendue aux quatre politiques |
| Le geste | Les modèles à jour sont posés en production le 27/08 — sauvegarde dans /home/servus/sauvegarde-modeles-20260827-170103. ⚠ Trois étaient périmés, pas un : charte (98 → 117 lignes), pseudos-interdits (137 → 165), pseudos-proteges (50 → 73). Les .conf n'ont pas été touchés — ils portent 84 et 12 entrées vivantes |
Éprouvé : test-conf-modele.sh passe de 9 à 15 constats, dont un qui regarde l'installation et non le dépôt — le seul du fichier à le faire. Passe de mutation à 5 blessures, sans trou, dont le retour de l'ancienne condition liée. ⚠⚠ Et le nouveau constat a rougi POUR DE VRAI avant que le geste soit fait : c'est la meilleure épreuve possible, celle qu'on ne peut pas fabriquer.
⚠ Ce que cela ne règle pas : Servers/ n'est distribué par aucun canal — décision encore ouverte au registre. La correction profite donc à la production et à qui clone le dépôt, pas encore à un tiers qui passe par l'installateur.
| Pour fermer | ⚠ Reste la moitié qui ne peut pas se faire sans décision, ET SANS L'EXPLOITANT : ce sont des mots de passe. Le mécanisme est écrit, éprouvé, documenté, et la procédure ci-dessus est vérifiée — ce qui manque n'est ni du code ni de la mesure, c'est le geste. ⚠ Il ne peut pas être automatisé : un mot de passe posé par un script serait connu du script. ⚠ Et il se fait des deux côtés, dans l'ordre donné plus haut : un seul des deux, et le robot se rabat sur son pseudonyme de repli sans rien dire. |
|---|
11. ✅✅ Un robot peut recevoir un salon où il ne peut pas entrer — CLOS le 14/08/2026, la suspension est partie à 18:00
Trouvé le 12/08/2026 par le constat de la bascule (point 2), et le défaut ne lui appartient pas : il est au-dessus, sur tout chemin qui confie un salon à un robot.
Rien ne vérifie que le robot POURRA entrer. Le mandat s'écrit actif, l'ordre part, le robot répond done — et il n'entre jamais. Sur #membres, qui porte +ntR, aucun des quatre robots ne le peut : ils portent +ix et ne sont identifiés auprès d'aucun service.
⚠ Le code le disait déjà, et personne n'en tirait les conséquences. Le commentaire d'::soc::mandat::installer prévient : « ceci rend ok quand l'ORDRE EST PARTI, jamais quand le robot est entré ». C'est exact et honnête. Mais aucun appelant ne regarde ensuite, et un droit accordé sur un salon inaccessible ne se distingue en rien d'un droit qui marche.
Ce que ça coûte, concrètement : le prêt paraît vivant côté base et côté panel, le salon n'a aucun modérateur, et le seul signal est une ligne d'écart au journal toutes les cinq minutes — soit 288 par jour. C'est très exactement le travers déjà corrigé deux fois sur ce chantier, retrouvé par une troisième porte.
✅ LA CAUSE EST ISOLÉE — contre-épreuve du 12/08/2026 à 15:28. Le même ordre, le même robot, sur un salon +nt cette fois :
15:28:14 ordre o1786541294-4 : #accueil activé, entrée en cours
15:28:15 OverOp-H joined #accueil.
Une seconde. La chaîne d'ordre n'a donc jamais été en cause : c'est le +R de #membres qui barrait l'entrée, et rien d'autre. Sans cette contre-épreuve, « le robot n'entre pas » aurait pu s'imputer au protocole, au robot ou à la bascule — trois pistes fausses.
⚠ Et ${t}_botreg_log le dit en différentiel, ce qui vaut mieux qu'un silence : 19 lignes error au total, les trois dernières à 14:01, 14:06 et 14:11 — une par cycle tant que le mandat désignait un salon inaccessible —, puis plus une seule. Le contrôle d'écart fonctionne, il a bien signalé le prêt impossible, et il se tait depuis que le prêt est servi. ⚠ Ne pas compter les lignes de cette table sans filtrer sur result : elle journalise aussi les done, et un COUNT(*) nu fait croire à un écart qui n'existe pas.
⚠ Les trois pistes ne se valent pas :
- refuser à l'octroi ce qu'on ne peut pas tenir — le plus honnête, mais il faut lire les modes du salon au bon moment, et ils changent après ;
- identifier les robots auprès d'
OverNick, ce qui leur donnerait+ret lèverait+R— mais c'est un compte et un mot de passe par robot, et cela touche le point 10 ; - constater après coup et défaire le mandat si le robot n'est pas entré au bout de N minutes — le contrôle d'écart voit déjà tout ce qu'il faut, il n'en tire simplement aucune conséquence.
⚠⚠ CE POINT ÉTAIT PÉRIMÉ DE MOITIÉ, et il présentait les trois pistes comme si aucune n'avait été prise. Mesuré le 13/08/2026 : la troisième — constater après coup — est écrite depuis le 12/08 (4.16.52, commit 813ca9a), et elle tourne en service. ::soc::mandat::non_servis relève les prêts dont le robot n'est pas là, signaler_non_servis prévient le journal et les opérateurs, au passage horaire, après la bascule de charge. La preuve est différentielle :
2026-08-13T17:00:00 [DEBUG] prêts : contrôle passé, tous les prêts actifs sont servis
⚠ Cette ligne n'est ni dans journalctl -u irix ni dans IriX.log — seulement dans IriX-sql.log. Un grep sur le premier journal aurait conclu que le contrôle ne tourne pas. C'est la règle des trois journaux, et elle vient de servir.
✅ LA MOITIÉ QUI MANQUAIT — DÉFAIRE — EST ÉCRITE LE 13/08/2026 (4.16.55). Tranché par SerVuS, qui renverse le choix de la veille : un prêt qu'aucun robot ne sert depuis 24 heures passe en suspendu. L'argument d'alors — un salon réservé par mégarde, corrigé dans l'heure — n'est pas écarté, il vit dans le délai : ce qui survit à vingt-quatre avertissements horaires n'est plus une configuration passagère.
⚠⚠ ET LA COLONNE ÉVIDENTE ÉTAIT LA MAUVAISE. updated_at était là, datée, vieillissante — elle mesure l'âge de la ligne, jamais celui du défaut. Le prêt de #accueil, servi et sans le moindre écart, portait 1582 minutes le jour où ce code a été écrit. Un seuil posé dessus aurait suspendu sur-le-champ un salon inaccessible depuis dix minutes, et épargné un salon mort depuis trois jours. Une colonne absent_depuis porte donc le premier constat d'absence, et rien d'autre.
⚠ Suspendre, jamais terminer : terminer est sans retour, et MANDATON <#salon> rétablit un prêt suspendu en un geste. L'annonce faite aux opérateurs dit comment le rétablir.
✅ LE CONSTAT EST LANCÉ, ET SA PREMIÈRE MOITIÉ EST FAITE. Un prêt d'essai a été accordé sur #membres (+ntR) le 13/08 à 18:21, à OverOp-S — un robot qui ne sert aucun autre salon.
19:00:00 [WARN] prêt : #membres est accordé à OverOp-S, et OverOp-S n'y est PAS…
19:00:00 [INFO] prêt : #membres n'est pas servi par OverOp-S — premier constat.
Suspension dans 24 h si rien ne change.
19:00:00 [INFO] prêts : 1 marqué(s) absent(s), 0 revenu(s), 0 suspendu(s)
20:00:00 [WARN] prêt : #membres est accordé à OverOp-S, et OverOp-S n'y est PAS…
20:00:00 [DEBUG] prêts : contrôle de suspension passé, rien à défaire
⚠⚠ C'EST LE SECOND CONSTAT, celui que ce carnet réclamait, et il est le plus difficile à obtenir : la suspension ne part pas avant le délai. Le passage de 20:00 le prouve — le prêt est toujours signalé absent, et rien n'est défait.
⚠ Et il ne prouverait rien sans la trace de vie de la 4.16.57. Un silence à 20:00 se lirait « le délai tient » exactement comme « le contrôle ne tourne plus ». C'est la ligne contrôle de suspension passé, rien à défaire qui fait la différence — écrite le jour même, après avoir constaté que ce code refaisait le défaut qu'il venait de corriger ailleurs.
✅✅ LA SUSPENSION EST PARTIE — 14/08/2026 à 18:00:00. LE CONSTAT EST COMPLET.
Les trois choses attendues, dans la même seconde :
18:00:00 [WARN] prêt : #membres SUSPENDU — OverOp-S n'y est pas entré depuis 24 h.
Le salon était annoncé modéré et ne l'était pas.
18:00:00 [INFO] prêts : 0 marqué(s) absent(s), 0 revenu(s), 1 suspendu(s)
GLOBOPS :[MANDAT] #membres : prêt SUSPENDU — OverOp-S n'y est jamais entré depuis
24 h. Le mandat reste ; MANDATON #membres le rétablit une fois
le salon accessible.
En base : etat = suspendu, updated_at = 18:00:00. Le mandat n'est pas terminé — c'est toute la décision de ce point, et elle tient : MANDATON le rétablira en un geste.
⚠ L'ÉCHÉANCE A ÉTÉ AVANCÉE D'UNE HEURE, ET IL FAUT LE DIRE. SerVuS a demandé à 17:30 de ne pas attendre 19:00. absent_depuis a été reculé de 19:00:00 à 17:55:00 sur la seule ligne d'essai — le seuil et le code n'ont pas bougé. Le déclenchement est donc resté entièrement automatique, ce qui est la seule chose qui compte pour un constat. ⚠ 17:45 avait été demandé et n'était pas atteignable : le contrôle est porté par un bind time horaire, qui ne part qu'à la minute :00.
✅ absent_depuis est repassé à NULL après la suspension, et c'est voulu : sans cet effacement, un prêt rétabli par MANDATON retomberait suspendu au passage horaire suivant, la vieille date d'absence dépassant toujours le seuil.
⚠⚠ ET LE CONSTAT A MONTRÉ AUTRE CHOSE, que personne ne cherchait : LA SUSPENSION N'A LAISSÉ AUCUNE TRACE EN BASE. ${t}_mandat_log n'a pas bougé — sa dernière ligne date toujours du 12/08. Le geste le plus lourd que ce mécanisme sache faire — retirer ses pouvoirs à un prêt — était invisible de tout ce qui lit la base, donc du panel. Il n'existait que dans deux fichiers de journal et dans un GLOBOPS que personne ne relit.
Corrigé le même jour, à la demande de SerVuS : voir « Le journal des prêts » ci-dessous, et PRET-ROBOT.md §9bis. ⚠ Le correctif n'était pas en service à 18:00 — le bot tournait le code chargé à 17:13 —, ce qui rend ce constat d'autant plus net : on a vu le manque en direct.
| Pour fermer | ✅ FERMÉ le 14/08/2026 — les deux moitiés sont constatées. La suspension ne part pas avant le délai (passage de 20:00 le 13/08, rien de défait, et la trace de vie pour le prouver), et elle part au terme (18:00 le 14/08). ⚠ Ce que ce point ne ferme PAS : #membres porte toujours +ntR et aucun des quatre robots ne peut y entrer, faute d'être identifié auprès d'un service. La suspension est le garde-fou, pas le remède — un robot identifiable est un travail à part entière, et il n'a pas de point à lui. ⚠ Le prêt d'essai (accorde_par = essai-suspension-13-08) est suspendu, pas nettoyé : il se périme seul le 16/08, et le laisser coûte moins qu'une écriture de plus en base. |
|---|
12. ✅✅ La console d'IriX — CLOSE le 12/08/2026, après cinq jours d'inexistence
Ce point n'a jamais figuré ici, et c'est précisément le défaut. Les trois commandes de console — .bots, .compte, .lien — ont été écrites le 07/08/2026, avec leur banc (18 constats, vert). Personne ne s'est demandé si quelqu'un pouvait les taper. Personne ne le pouvait.
Cinq jours durant, Web/pages/eggdrop.php a annoncé au panel une console qui refusait tout le monde, et test-partyline.tcl est resté vert sans qu'aucune de ces commandes ait servi une fois. C'est le travers de PRET-ROBOT.md en entier : « écrit et éprouvé » n'est pas « en service ».
⚠ DEUX défauts empilés, et le second ne se voyait qu'une fois le premier levé. C'est la forme que prend toujours ce genre de pile — on ne trouve la marche suivante qu'en montant la première.
① L'accès : le socket écoutait, et refusait.
[12/08 16:46:08] Connexion telnet : localhost/49857
[12/08 16:46:08] Telnet refusé : telnet@localhost, aucun accès
SerVuS portait trois --HOSTS au userfile, tous des masques IRC (*!*@Clk-….mshome.net…). Aucun ne couvre une connexion telnet locale, dont le masque est -telnet!telnet@localhost. Les quatre OverOp, eux, portent -telnet!*@* — d'où la différence, et d'où la méthode : comparer ce qui marche à ce qui ne marche pas, plutôt que lire le code de celui qui échoue.
⚠ Ce n'était NI le flag +p, NI require-p, NI le réseau — les trois ont été mesurés et écartés un par un. SerVuS porte jlmnoptx sur IriX comme sur les OverOp ; require-p 1, posé sur IriX seul, était donc satisfait ; et depuis Windows, 127.0.0.1:3341 rendait déjà la bannière d'OverOp-H, WSL tournant en réseau miroir. Un refus au niveau applicatif ressemble beaucoup à un blocage réseau, et se soigne à un tout autre endroit.
Levé à froid — Eggdrop tient son userfile en MÉMOIRE et le réécrit à l'heure ronde, donc une édition à chaud aurait disparu à 18:00 sans un mot. Deux masques ajoutés : -telnet!*@localhost et -telnet!*@127.0.0.1, et non -telnet!*@* : le listen borne déjà l'écoute à la boucle locale, le userfile n'a pas à être plus ouvert que lui.
② Le code : .lien appelait sa voisine au lieu d'Eggdrop.
[12/08 17:41:25] Tcl error [::soc::dcc::lien]: wrong # args: should be "bots hand idx text"
::soc::dcc::lien faisait set lies [bots], voulant la commande Eggdrop du core qui rend la liste des bots liés. Mais depuis le namespace ::soc::dcc, Tcl résout un nom court dans le namespace courant avant le global — et ::soc::dcc::bots existe, c'est la procédure d'à côté. Corrigé en [::bots] : deux caractères.
⚠ Le message d'erreur nomme bots alors qu'on a tapé .lien, et parle d'un nombre d'arguments là où l'appel n'en passe aucun. Il envoie chercher au mauvais endroit.
⚠⚠ ET LE BANC NE POUVAIT PAS L'ATTRAPER. test-partyline.tcl remplace bots par un faux dans son namespace d'essai — il éprouvait donc exactement le chemin qui n'existe pas en service. C'est une quatrième façon de mentir, à ajouter aux trois de la fiche irix-confirmation-mensongere : un doublet de nom entre une procédure et une commande de l'hôte ne se voit qu'en vrai.
Constaté en service, journal à l'appui : #SerVuS# compte (17:41:29), #SerVuS# lien (17:45:38), #SerVuS# bots (17:45:45), après Connecté : SerVuS (telnet@localhost/42212).
⚠ CE QUI RESTE, ET QU'ON NE PRÉTEND PAS FERMÉ :
- ✅✅ Le DCC chat — CONSTATÉ le 12/08/2026 à 19:16:02. Il échouait pour une cause distincte du telnet : le bot annonçait
192.168.1.254, la passerelle par défaut, où rien n'écoute. Le client affichait « Unable to connect » pendant que le journal du bot ne portait aucun refus — parce qu'il n'y en avait pas : la connexion n'arrivait jamais.set nat-ip "127.0.0.1"l'a réglé, et la preuve est au journal :
19:15:51 CTCP CHAT: from SerVuS (Tgl0be@aodxl5.olsury.18pt7j.IP)
19:15:53 Connexion telnet : localhost/1024
19:16:02 Connecté : SerVuS (telnet@localhost/1024)
⚠⚠ ET LES DEUX CORRECTIFS DU JOUR ÉTAIENT NÉCESSAIRES, chacun pour sa moitié. Le masque de la connexion est telnet@localhost — celui ajouté à froid le matin même : un CTCP CHAT n'ouvre pas de port dédié, il fait revenir le client sur le port d'écoute telnet (3333). Sans nat-ip, le client ne trouvait pas le bot ; sans le hostmask, il y serait arrivé pour se faire refuser. Deux pannes superposées sur un même chemin, et corriger l'une seule n'aurait rien montré de l'autre.
⚠ nat-ip vaut 127.0.0.1 : le DCC ne marchera que depuis cette machine. C'est cohérent avec un listen borné à la boucle locale — mais c'est un choix, pas une neutralité.
- Un « Quoi ? Essayez '.help' » sur
.botsà 17:38 est resté sans explication. La commande a fonctionné sept minutes plus tard sans qu'on ait rien changé qui la concerne — la correction ne touchait quelien, et la version en production était identique àHEAD(vérifié).
⚠ Recherche ABANDONNÉE, tranché par SerVuS le 12/08/2026. Ce n'est pas une tâche en attente : la commande sert depuis, les trois binds sont posés à chaque rehash, et rien ne laisse penser que l'incident revienne. On l'écrit parce qu'on ne le comprend pas, pas parce qu'on le soupçonne. À rouvrir seulement s'il se reproduit — auquel cas la fenêtre à capturer est celle des secondes qui suivent un .rehash.
- Les quatre OverOp n'ont AUCUN
bind dcc, ni en production ni au dépôt. Leur console n'offre que le natif Eggdrop. Ce n'est pas un défaut — ils reçoivent leurs ordres par le protocole OverBot — mais la page du panel l'affirmait, et elle a été corrigée le même jour.
| Pour fermer | ✅ FERMÉ le 12/08/2026 — les trois commandes constatées en service sur une console réellement ouverte, après cinq jours où elles existaient sans que personne puisse s'y connecter. ⚠ Cette ligne manquait jusqu'au 14/08/2026 : ce point était le seul des seize à n'en porter aucune, alors que c'est elle qui dit pourquoi un point est clos plutôt que de le laisser supposer. ⚠ Deux choses restent vraies sans le rouvrir : le DCC ne marche que depuis cette machine (nat-ip 127.0.0.1, cohérent avec un listen borné à la boucle locale — mais c'est un choix, pas une neutralité) ; et le « Quoi ? » sur .bots du 12/08 à 17:38 n'a jamais été expliqué, recherche abandonnée sur décision de SerVuS. À rouvrir seulement s'il se reproduit, auquel cas la fenêtre à capturer est celle des secondes qui suivent un .rehash. |
|---|
| Ce que ça coûte de ne pas y penser | Une fonction livrée, documentée au panel, et couverte par un banc vert peut n'avoir jamais été exécutée une seule fois. Aucun de ces trois signaux ne dit qu'elle sert. ⚠ Le seul qui le dise est le journal du service qui l'exécute — ici #SerVuS# bots, absent pendant cinq jours. |
|---|
13. ⏹️ L'IPv6 est un chemin d'entrée que la documentation niait — CLOS SANS MESURE le 25/08/2026
⏹️ FERMÉ SUR DÉCISION DE SERVUS, LE 25/08/2026 : « je ne pourrai pas le tester ».
⚠⚠ CE POINT N'EST PAS ✅, ET LA DIFFÉRENCE EST TOUT. Un ✅ dirait « constaté sûr ». Ici, rien n'a été mesuré et rien ne le sera : la sonde exigeait une machine extérieure au préfixe
/64, et cette condition ne peut pas être réunie. Le marquer ✅ serait exactement le défaut que ce registre traque — une confirmation mensongère.⚠ CE QUI RESTE VRAI, ET QUI NE S'EFFACE PAS AVEC LE POINT :
.wslconfigporte toujoursnetworkingMode=mirrored, donc la distribution partage l'IPv6 publique de l'hôte. C'est un fait mesuré, pas une hypothèse.- Le pare-feu Freebox n'a jamais été éprouvé sur ce chemin. On ne sait pas s'il filtre l'IPv6 entrante — ni dans un sens, ni dans l'autre.
- Les trois documents qui niaient ce chemin (
RESEAU.md,WEB.md,PROJET.md) ont bien été corrigés le 12/08 : ce travail-là reste acquis, et c'était le plus utile des deux.⚠ Ne pas rouvrir ce point pour redemander la sonde. Si la question revient un jour, elle reviendra par un autre chemin : un accès depuis l'extérieur qui existe déjà (un hébergeur, un téléphone hors Wi-Fi domestique), et non par un dispositif à monter pour l'occasion.
Ouvert le 12/08/2026. Ce n'est pas une faille constatée : c'est un chemin réseau que trois documents déclaraient inexistant, et que personne n'a jamais éprouvé.
⚠ Le fait, mesuré : .wslconfig porte networkingMode=mirrored. La distribution partage donc les interfaces de l'hôte, IPv6 publique comprise :
eth2 192.168.1.27/24 2a01:e0a:d83:8c50::2a79:9f0d/128
default via fe80::2266:cfff:fe60:1241 dev eth2
⚠ Ce que trois documents affirmaient, et qui était faux : RESEAU.md décrivait les deux modes sans dire lequel tournait — le lecteur retenait le plus rassurant. WEB.md et PROJET.md justifiaient inet_protocols = ipv4 par « la machine n'a pas de route IPv6 ». Les trois ont été corrigés le jour même. Ces phrases étaient probablement vraies quand WSL tournait en NAT : c'est le passage en mode miroir qui les a périmées, sans que rien ne le signale — le même mécanisme que « trois robots sur 60 places » au point 2.
Ce qui est mesuré, et qui ne suffit pas à conclure :
| Élément | État constaté le 12/08/2026 |
|---|---|
| Serveur IRC | écoute sur ip * pour 6667 et 6697 — donc sur l'IPv6 publique. Le lien serveur (6900) et le websocket (8000) restent en 127.0.0.1 |
net.ipv6.bindv6only | 0 — un socket [::] accepte AUSSI l'IPv4 mappée : un port dual-stack est joignable par les deux familles même sans ligne IPv4 dans ss |
| Pare-feu Windows | actif sur les trois profils ; n'ouvre en entrant que 6697, 8880, 8443 (profil Any). 6667 en clair, SSH, les ports de messagerie et 8123 tombent sur le refus par défaut |
| Pare-feu de la distribution | ⚠ MESURE NULLE, pas un constat d'absence — remesuré le 14/08 : ip6tables -S répond « Permission denied (you must be root) », nft list ruleset rend zéro ligne sans droits. Ne pas pouvoir mesurer n'est pas constater qu'il n'y a rien |
| Pare-feu IPv6 de la Freebox | non mesuré — on n'ouvre pas de session sur le panel Freebox sans l'exploitant |
⚠ En IPv6 il n'y a pas de NAT, donc pas de redirection à franchir. Toute l'analyse d'exposition du projet raisonne sur les redirections de ports — elles ne valent que pour l'IPv4. En IPv6, le pare-feu de la Freebox est seul devant celui de Windows.
✅ REMESURÉ LE 14/08/2026 — rien n'a bougé sur l'essentiel : l'adresse est identique (2a01:e0a:d83:8c50::2a79:9f0d/128), donc la cible de sonde ci-dessous reste valide ; networkingMode=mirrored tient ; le serveur écoute toujours sur * pour 6667 et 6697 tandis que 6900, 8000 et 8080 restent en 127.0.0.1 ; le nom public n'a toujours aucun AAAA (getent ahostsv6 ne rend qu'une IPv4 mappée) ; et le pare-feu Windows n'ouvre bien que 6697, 8443, 8880, profil Any.
⚠⚠ L'INVENTAIRE DES PORTS EXPOSÉS ÉTAIT INCOMPLET. Ce point cite 6667, 6697, la messagerie et 8123. Mesuré le 14/08, onze ports écoutent sur [::] ou *, donc sur l'IPv6 publique :
| Port | Qui, mesuré |
|---|---|
| 22 | ssh |
| 110 · 143 · 993 · 995 | dovecot — la messagerie |
| 6667 · 6697 | overtchatd (pid 271) — le serveur IRC |
| 8443 · 8880 | Apache |
| 8123 | ✅ Home Assistant — prouvé le 14/08 par ses en-têtes (Server: vide, X-Frame-Options: SAMEORIGIN) et par son processus, python3 -m homeassistant --config /config |
| 18555 | ✅ go2rtc, module WebRTC — prouvé le 14/08 par sa propre configuration : webrtc: listen: ":18555/tcp" |
✅ LES DEUX SONT IDENTIFIÉS — 14/08/2026. Ils viennent du même conteneur : homeassistant (ghcr.io/home-assistant/home-assistant:stable), qui embarque go2rtc pour les flux de caméras.
⚠⚠ ET C'EST network_mode: host QUI LES AMÈNE SUR L'IPv6 PUBLIQUE. Le conteneur ne publie aucun port — docker inspect rend Ports : vide et aucun docker-proxy ne tourne —, il partage directement la pile réseau de l'hôte. Ses ports ne sont donc pas « redirigés » : ils sont ceux de la machine, IPv6 comprise. Un raisonnement en redirections de ports ne les voit pas passer.
⚠ Le contraste vaut d'être noté : dans la même configuration, go2rtc limite son RTSP à 127.0.0.1:18554 et n'expose son API que sur une socket Unix — mais son WebRTC écoute sur :, donc toutes les interfaces. C'est le fonctionnement normal de WebRTC, qui doit être joignable ; sur une machine à IPv6 publique et sans pare-feu mesurable côté distribution, cela se dit.
⚠ Aucun des deux n'est ouvert par le pare-feu Windows, qui reste le seul rempart mesuré.
📌 Le piège qui a coûté le plus de temps ici, et qu'il faut garder : docker ps échouait sur /var/run/docker.sock: no such file or directory, ce qui laissait croire qu'aucun conteneur ne tournait. Le socket est ailleurs — docker.socket porte un override.conf qui le place sur /run/docker-native.sock. Avec DOCKER_HOST=unix:///run/docker-native.sock, tout devient lisible sans le moindre sudo, l'utilisateur étant dans le groupe docker. Voir aussi le double daemon de ce projet, Docker Desktop contre dockerd de la distribution.
⚠⚠ ET LA SEULE MESURE QUI TRANCHERAIT NE PEUT PAS SE FAIRE D'ICI. Une sonde lancée depuis la machine ne quitte pas l'hôte : elle prouve seulement qu'un service écoute, jamais qu'il est joignable. C'est le même angle mort que le X-Forwarded-For, qui a demandé un vrai visiteur pour être fermé — voir « Ce qui demande la présence de l'exploitant ».
⚠⚠ LE PIÈGE DE CETTE SONDE, ET IL A COÛTÉ TROIS ESSAIS LE 12/08/2026. La sonde doit partir d'une autre machine. Pas d'un autre réseau : d'une autre MACHINE. Le poste Windows héberge WSL, donc les services : quel que soit le réseau auquel on le branche — y compris un partage de connexion mobile — il se teste lui-même, et la connexion fait demi-tour sans franchir ni le pare-feu de la Freebox ni le chemin d'Internet.
✅ LA MÉCANIQUE EST MESURÉE depuis le 16/08/2026, elle n'est plus une supposition :
ip -6 route get 2a01:e0a:d83:8c50::2a79:9f0d
→ local 2a01:e0a:d83:8c50::2a79:9f0d from :: dev lo table local proto kernel
^^^^^^ ^^^^^^^^^^
Le noyau range sa propre adresse publique dans la table local et l'oriente sur lo. Un paquet émis vers elle depuis la machine n'atteint jamais la carte réseau : ni pare-feu, ni Freebox, ni Internet. Find-NetRoute côté Windows rend un NextHop vide sur la même adresse. C'est pourquoi changer de réseau ne change rien — et c'est très probablement l'erreur du 12/08.
Les critères qu'il faut regarder AVANT le verdict — il y en a trois, pas un :
SourceAddress : 2a01:e0a:d83:8c50::2a79:9f0d
RemoteAddress : 2a01:e0a:d83:8c50::2a79:9f0d ← identiques : la mesure ne vaut rien
RTT : 0 ms ← et 0 ms le confirme
⚠⚠ ET LE TROISIÈME MANQUAIT, alors qu'il est le plus perfide. SourceAddress peut différer de RemoteAddress tout en restant dans le foyer : un téléphone encore sur le Wi-Fi, ou une adresse temporaire RFC 4941, donne deux adresses distinctes du même préfixe. Le vrai critère est donc : l'adresse source doit être HORS du préfixe 2a01:e0a:d83:8c50::/64. Valider sur SourceAddress ≠ RemoteAddress seul laisse passer une mesure nulle qui a l'air bonne.
⚠ Et ces trois critères viennent de Test-NetConnection. Un téléphone en données mobiles n'a pas PowerShell : sur mobile, la vérification équivalente est de relever l'adresse source affichée par https://ifconfig.co ou https://test-ipv6.com avant la sonde, et de contrôler qu'elle ne commence pas par 2a01:e0a:d83:8c50:.
⚠ Étape 0 obligatoire, sinon l'échec est un faux négatif : couper la radio Wi-Fi (pas seulement se déconnecter), couper tout VPN, et vérifier que l'opérateur mobile donne bien l'IPv6. Si la page affiche une adresse en 10., 100.64. ou une IPv4, arrêter — certains opérateurs n'ont pas d'IPv6 native en 4G/5G, et tout échec ne prouverait alors rien.
Ce qui distingue un vrai succès d'un faux, en une phrase : du contenu produit par overtchatd ou Apache — une bannière IRC, la page du site —, obtenu depuis une adresse hors du /64, avec une latence non nulle. « Connecté » sans bannière, un socket ouvert, une page quelconque, 0 ms : autant de faux succès. ⚠ Et sur […]:8443, l'avertissement de certificat est normal et attendu (il est émis pour le nom, pas pour une IP littérale) — ce n'est pas un échec.
⚠ Un scanner IPv6 tiers sans compte (ipv6.chappell-family.com, ipv6scanner.com, subnetonline) est un complément utile : il ne peut structurellement pas se tester lui-même, donc le demi-tour n'existe pas pour lui. Mais il ne dit pas lequel des trois pare-feu bloque, et un « ouvert » sur 6697 ne prouve pas qu'overtchatd répond — seulement qu'un socket accepte. Deux chemins indépendants qui concordent valent mieux qu'un seul.
⚠ Et il publie l'adresse cible dans un journal tiers. Ce n'est pas rédhibitoire — elle est publique par nature — mais c'est un choix à faire sciemment.
❌ CE POINT AFFIRMAIT UNE CHOSE FAUSSE SUR LE PARE-FEU. Il écrit que le pare-feu Windows « n'ouvre en entrant que 6697, 8880 et 8443 ». Mesuré le 16/08 : six règles « Service-Overtchat » en profil Any — 80 (deux règles), 443, 6697, 8443, 8880 ; plus, côté pare-feu Hyper-V (celui qui gouverne réellement WSL en mode miroir), phpMyAdmin local (8090). La conclusion tient par accident — rien n'écoute sur 80 ni 443, Apache étant sur 8880/8443 — mais ce sont trois portes ouvertes sans personne derrière : le jour où un service prend le 80, il est exposé sans qu'aucune règle nouvelle soit créée. C'est une exposition en sommeil.
✅ En revanche le refus par défaut n'est plus une supposition : Get-NetFirewallProfile -PolicyStore ActiveStore rend Enabled=True et DefaultInboundAction = Block sur les trois profils, et Get-NetFirewallHyperVVMSetting rend Block lui aussi. Seul le pare-feu Freebox reste non mesuré — c'est lui, et lui seul, que la sonde extérieure éprouvera.
⚠⚠ ET L'ENJEU DÉPASSE CE POINT. L'inventaire des 11 ports ne compte que la distribution. En mode miroir, l'hôte Windows partage la même adresse IPv6 et écoute aussi — dont 445 (SMB) et 135 (RPC). Or, mesuré le 16/08 : l'interface Wi-Fi est en profil Public, et quatre règles y sont actives avec RemoteAddress = Any — FPS-SMB-In-TCP-V2 (445) et les trois du spouleur d'impression (RPC, RPCEPMap). Leurs jumelles Domain/Private sont désactivées. Ce que la sonde doit éprouver n'est donc pas seulement « mon serveur IRC est-il visible », c'est « le partage de fichiers Windows l'est-il ». Geste de SerVuS pour les borner : Set-NetFirewallRule -Name FPS-SMB-In-TCP-V2,FPS-SpoolSvc-In-TCP-V2,FPS-SpoolWorker-In-TCP-V2,FPS-RPCSS-In-TCP-V2 -RemoteAddress LocalSubnet
⚠ Les trois essais du 12/08 ont rendu TcpTestSucceeded : False sur 6697, 6667 et 22. Ce ne sont pas trois ports fermés : ce sont trois mesures nulles. Une sonde lancée depuis WSL le même jour répondait l'inverse sur 6697 — les deux sont également sans valeur, et se contredire ne les rend pas plus vraies.
| Pour fermer | Une sonde depuis une autre machine sur [2a01:e0a:d83:8c50::2a79:9f0d] — un téléphone en données mobiles, Wi-Fi coupé, ou un poste hors du foyer. ⚠ Vérifier d'abord que l'appareil a bien l'IPv6 (curl -6 ifconfig.co), sinon la sonde échoue pour la mauvaise raison. Trois ports : 6697 (attendu joignable, c'est le service), 6667 et 22 (attendus refusés). ⚠ Et SourceAddress doit différer de RemoteAddress, sans quoi le résultat est nul quel qu'il soit. |
|---|
⚠ Le nom public ne mène pas à l'IPv6 : service-overtchat.hd.free.fr n'a aucun enregistrement AAAA (vérifié le 12/08). L'adresse littérale est donc le seul chemin — ce qui écarte les applications de test qui n'acceptent qu'un nom de domaine. Non découvrable par le nom n'est pas inaccessible pour autant.
17. ✅✅ Le journal des prêts — EN SERVICE et CONSTATÉ le 14/08/2026 à 18:32:08
Demandé par SerVuS le 14/08/2026 : « un journal pour le prêt de bot, pour voir toutes les actions », visible dans la partie admin du site.
Le fait : le journal existait depuis le 03/08 et ne captait presque rien. Croisement des 32 procédures du module contre leurs appels à journaliser — deux journalisaient, l'accord et l'expiration.
| Ce qui n'écrivait rien | Ce que ça coûtait |
|---|---|
basculer — suspension et reprise | un prêt perdait ses pouvoirs sans qu'on sache quand ni pourquoi |
terminer — la fin manuelle | l'auteur de la fin n'était nulle part |
_reprendre_un — la bascule de charge | un salon changeait de robot en silence |
installer / retirer — les ordres JOIN et PART | aucun lien entre « le droit est accordé » et « l'ordre est parti » |
| le premier constat d'absence, et le retour | le compte à rebours de 24 h démarrait sans témoin |
⚠⚠ LA PREUVE EST TOMBÉE EN DIRECT, À 18:00 LE JOUR MÊME. La suspension du point 11 est partie, GLOBOPS compris — et ${t}_mandat_log n'a pas bougé d'une ligne. Le geste le plus lourd du mécanisme était invisible de tout ce qui lit la base. On ne l'a pas déduit : on l'a regardé ne pas s'écrire.
✅ Ce qui est livré (14/08/2026) :
- côté bot — le journal s'écrit dans
basculeretterminer, les deux portes par lesquelles un prêt change d'état, plus les deux ordres réseau et les deux constats d'absence. ⚠ Aux portes, et non chez les appelants : les y laisser aurait donné autant de rédactions que de chemins, et le prochain chemin ajouté n'écrirait rien sans que rien ne le dise. ⚠ Les refus sont journalisés aussi — un journal qui ne garde que les réussites répond « rien ne s'est passé » à qui cherche pourquoi une suspension attendue n'a pas eu lieu ; - neuf actes :
accord·fin·suspension·reprise·bascule·absence·retour·entree·sortie; - côté panel —
?p=robotsporte « Prêts de robot — l'état de chacun » et « Journal des prêts — toutes les actions », filtrable par acte.Web/inc/mandat.phpne porte aucune écriture, et le banc le vérifie ; - éprouvé —
test-mandat.tclà 199 constats,test-mandat-journal.phpà 18, et la page rendue hors navigateur : 5 tableaux, 81 lignes, zéro notice.
⚠ Deux confirmations mensongères attrapées en l'écrivant. Le banc de croisement prenait "suspendu" pour un acte — c'est l'issue, lue dans la condition d'un ternaire : un croisement qui extrait trop accuse le code juste, et l'on est à deux doigts d'ajouter à la page une entrée qui n'aurait jamais dû exister. Et le rendu de page annonçait quatre notices PHP qui venaient d'un echo du script de mesure, pas du panel — soupçonner l'outil avant le code, une fois de plus.
✅✅ CONSTATÉ EN SERVICE — 14/08/2026 à 18:32:08. SerVuS a demandé de « redémarrer tout ce qu'il faut pour que les journaux soient écrits ». IriX redémarré à 18:30:19, puis un seul essai : MANDATON #membres envoyé à OverBot par ClaudeOps, en TLS sur 6697.
OverBot → « Prêt de #membres rétabli. »
| 12 | #membres | OverOp-S | ClaudeOps | reprise | ok | MANDATON par ClaudeOps | 18:32:08 |
Et vu DANS LA PAGE, rendue depuis le code de production : « 18:32:08 · Rétabli · #membres · OverOp-S · ClaudeOps · MANDATON par ClaudeOps », avec le filtre « Rétabli (1) ». Le journal passe de 9 lignes figées au 12/08 à 10, la dernière du jour.
| Ce qui était exigé | Ce qui a été mesuré |
|---|---|
une ligne acte=reprise, result=ok | ✅ id 12 |
| l'auteur nommé | ✅ ClaudeOps, et non « automatique » — c'est le paramètre ajouté à MANDATON qui le porte |
| la voir dans la page | ✅ rendue depuis /home/servus/…, 5 tableaux, 82 lignes |
| une seule ligne pour l'acte | ✅ 1 — le doublon de MANDATFIN avait été retiré à temps |
⚠⚠ LE DOUBLON A ÉTÉ TROUVÉ EN PRÉPARANT CET ESSAI, pas en relisant le code. MANDATFIN journalisait fin de son côté depuis le 05/08 ; terminer s'y étant mise le 14/08, une fin par IRCop aurait posé deux lignes, dont une sans auteur. Le geste qui allait éprouver le mécanisme a montré son défaut avant lui — et aucun banc ne l'aurait vu, les deux écritures étant justes chacune de son côté.
⚠ Ce qui N'A PAS été éprouvé, et ne le sera pas facilement : le refus. basculer journalise result=error quand la base refuse l'UPDATE — un cas qu'on ne provoque pas sans casser quelque chose. Un salon sans prêt sort avant l'écriture et ne journalise rien, ce qui est correct mais n'éprouve pas cette branche. Elle reste couverte par le banc seul, et c'est une limite à connaître plutôt qu'un travail à faire.
✅✅ ET LA PREUVE QUI VAUT PLUS QUE L'ESSAI EST TOMBÉE À 19:00:00 — SANS PERSONNE. Le passage horaire a constaté l'absence d'OverOp-S sur #membres et l'a écrite au journal de lui-même :
| 13 | #membres | OverOp-S | balayage automatique | absence | absent |
| premier constat — suspension dans 24 h si rien ne change | 19:00:00 |
⚠ C'est un meilleur constat que le mien. MANDATON prouvait qu'un acte commandé s'écrit ; celui-ci prouve que le mécanisme écrit quand personne ne regarde, ce qui est la seule chose qui compte pour un journal. Le demandeur porte « balayage automatique », le result vaut absent — distinct d'error, parce qu'une absence est un état constaté, pas une panne.
Une ligne par acte, aucun doublon : absence 1, reprise 1.
| Ce qui reste | Rien pour ce point. ⚠ Deux conséquences à savoir. (1) #membres est repassé actif et OverOp-S ne peut toujours pas y entrer — le salon porte +ntR, point 11. Le compte à rebours des 24 h a redémarré à 19:00:00 : il sera suspendu demain à la même heure, et cette fois le journal l'écrira. (2) Le prêt d'essai se périme seul le 16/08. |
|---|
⚠ Les endroits où ça résiste
⚠ Un essai sur un salon armé en antimot fait apprendre l'IA
Trois règles qui survivent à la fermeture du point 1, et qu'on ne lira plus là où on les cherchait depuis qu'il a rejoint les travaux :
- ne jamais injecter de jetons d'essai pour « prouver que ça marche » : du vocabulaire inventé fausserait durablement tous les scores à venir ;
- ⚠⚠ tout essai sur un salon armé en apprend, même sans le vouloir. Le 10/08/2026, quatre commandes
!newban/!delbanlancées sur#accueilont fait passeria_tokensde 0 à 16 —amorce_okapprendgoodsur tout message que le filtre laisse passer, et une commande en est un. Personne n'avait rien injecté. Un essai se fait donc sur#test, seul salon du bot oùantimotvautnon(vérifié le 14/08/2026), ou se termine par une purge ; ia_tokensà 0 n'est pas une panne, c'est l'état voulu tant que personne n'est venu. Le lire comme un défaut a déjà coûté une fausse piste.
Détail, preuves et chaîne complète : point 1.
Ce ne sont pas des défauts : ce sont les pièges qui reviennent, et qui coûtent du temps à chaque fois qu'on les oublie.
✅ Un SQUIT d'un ULine emportait l'autre — c'était IriX, pas le serveur
Constaté le 11/08/2026 en arrêtant Anope proprement. IriX tombait avec lui, systématiquement.
⚠⚠ J'AI D'ABORD ACCUSÉ LE SERVEUR, ET C'ÉTAIT FAUX. Ce paragraphe a porté pendant une heure « le serveur propage le SQUIT d'un ULine à l'autre », avec une piste vers src/cmd/ et une recompilation en perspective. La mesure a dit l'inverse : le journal d'IriX montre qu'il émet lui-même son SQUIT et se déconnecte.
17:26:38 RAW OUT: SQUIT IriX.overtchat.fr :SQUIT reçu
17:26:38 [Overtchat] déconnecté — SQUIT reçu
Le serveur faisait son travail : un netsplit se relaie à tous les liens, c'est ainsi qu'ils l'apprennent. La branche SQUIT du dispatcher d'IriX, elle, se déconnectait sur tout SQUIT passant sur le lien, sans jamais regarder sa cible.
⚠ Le raccourci a tenu des mois parce qu'IriX était le seul ULine : un SQUIT arrivant sur ce lien était forcément le sien. Le défaut ne pouvait apparaître qu'à partir du deuxième service. Aucune relecture ne l'aurait montré — le code était juste pour le réseau qu'il avait sous les yeux.
⚠ Le coût s'est mesuré le même jour : Restart=on-failure relançait Anope toutes les dix secondes après un refus de protocole, et chaque tour emportait IriX. Douze tours.
✅ Corrigé en 4.16.37 — ::soc::ulines::auto::squit_nous_vise compare la cible à notre nom de serveur et à notre SID, sans casse. Banc IriX/tests/test-squit.tcl : 12 constats, 4 mutations, dont celle qui remet l'ancien code.
✅ CONSTATÉ EN RÉEL le 11/08/2026 à 17h43. Anope monté, lié (17 clients), puis arrêté :
:005 SQUIT services.overtchat.fr :Services terminating via signal Terminated (15)
SOC ULINES AUTO: [Overtchat] SQUIT sur « services.overtchat.fr » — ce n'est pas nous, le lien tient
Zéro netsplit d'IriX, son lien inchangé de part et d'autre de l'épreuve, les cinq services toujours là et le prêt de #accueil intact. C'est exactement le scénario qui coûtait un netsplit une heure plus tôt.
La leçon, et elle vaut mieux que le correctif : le premier journal lu accusait le serveur (« Rupture du lien avec IriX.overtchat.fr : SQUIT reçu »), et cette ligne EST du serveur — elle décrit ce qu'il fait, pas qui l'a demandé. Lire les deux journaux avant de désigner un coupable. Voir aussi « Les journaux ne sont pas là où on les cherche », ci-dessous.
Les journaux ne sont pas là où on les cherche
::soc::log écrit dans IriX/cmds/IriX-sql.log, PAS dans journalctl. Seul ce qui passe par putlog arrive au journal systemd. Chercher une trace ::soc::log par journalctl ne rend rien, et ce vide se lit comme « le mécanisme ne tourne pas ».
⚠ Ce seul piège a produit trois conclusions fausses le même jour (07/08/2026), dont un recul sur du code qui fonctionnait parfaitement.
Les délais qu'on sous-estime
| Ce qu'on attend | Le vrai délai |
|---|---|
systemctl restart irix → les 9 connectés reviennent | 75 s, pas 25. Le lien S2S doit se refaire ET réintroduire les cinq pseudo-clients |
| Un réglage écrit en base → appliqué par le bot | jusqu'à 30 s (::soc::auto::scan_config) |
${t}_online | jusqu'à 30 s de retard, et vidé par un HUP |
⚠ Un réglage auto_config ne demande AUCUN redémarrage. ::soc::auto::scan_config (IriX-sql.tcl:9862) compare l'updated_at de chaque salon à ce qu'il a vu, et vide le cache du salon qui a bougé — antimot et antimot_action compris. Mesuré le 09/08/2026 : 3 secondes entre l'UPDATE et la ligne « config de #!Overtchat! modifiée hors du bot — cache relu ». Un restart irix lancé pour cela coûte 75 s de coupure pour rien.
Cela ne vaut que pour auto_config : cache_invalidate n'efface que ces clés-là.
Conclure d'une seule lecture est l'erreur la plus fréquente de ce projet.
Les schémas qu'on croit connaître
Cinq colonnes nommées de mémoire le 07/08/2026, aucune n'existait : server dans _online, type pour event_type, la table IriX_cmds, grade pour chan_access.grade_id, asked_by pour botreq.demandeur.
Deux de plus le 09/08/2026, sur les tables de l'IA :
auto_confign'a pas de couplesetting/value. Ses réglages sont des colonnes en dur :antimot,antimot_action,antiflood_lines… Une requêteWHERE setting LIKE …lève 1054 ;ia_inboxetia_outboxdatent parcreated, pascreated_at.
Et deux de plus le 12/08/2026, dans la même séance : server n'existe pas dans ${t}_online (la colonne d'origine est is_svc, et l'hôte est host), ni expires dans ${t}_mandat. La seconde a été nommée une heure après avoir relu le schéma de la première.
⚠ Le compte importe moins que la leçon : ce ne sont jamais des noms absurdes, ce sont ceux qu'une table devrait avoir. Les deviner juste huit fois sur dix est précisément ce qui rend le neuvième coûteux — la requête échoue, et si un catch l'avale, elle échoue en silence. SHOW COLUMNS d'abord, systématiquement.
⚠ Et côté PHP, db_scalar() n'existe pas — c'est du Tcl. Le site offre q, q1, qv, qx, tbl, column_exists, table_exists. qv() rend une valeur unique.
⚠ Le pire cas : un DELETE de nettoyage visant une colonne inexistante. Il a échoué, le catch l'a avalé, et une ligne d'essai est restée en production sans que rien ne le dise. Un nettoyage qu'on ne constate pas n'est pas un nettoyage.
Les listes écrites à la main
Bootstrap/overtchat-setup.shporte deux listes de modules (git.irix.filesetmodes.sql.modules) — le hook pre-commit les contrôle depuis le 31/07/2026 ;tests/run-tests.shdu serveur énumère ses campagnes, contrairement àTools/bancs.shqui découvre par glob. Une campagne écrite sans y être inscrite ne tourne jamais ;- ⚠⚠ ET « DÉCOUVRIR PAR GLOB » NE COUVRE QUE CE QUI A UN MOTIF. Cette ligne-ci innocentait
Tools/bancs.shalors qu'il n'avait aucun motif bash :Bootstrap/overtchat-setup.shfait dix mille lignes, décide de l'installation entière, et n'était couvert par rien. Trouvé le 11/08/2026 en écrivant le premier banc bash — pas par une relecture, mais parce que le lanceur ignorait le banc qu'on venait de poser. Une famille sans motif ne se voit pas : le lanceur ne se plaint jamais d'un banc qu'il n'a pas trouvé, il annonce « tout est au vert », ce qui est vrai de ce qu'il regarde. Motif ajouté (Bootstrap/tests/,Tools/tests/). ⚠ La question à reposer périodiquement n'est pas « les bancs passent-ils ? » mais « quelles familles de fichiers n'ont aucun banc ? » — Python et SQL n'en ont toujours aucun ; - ⚠
${t}_commandsn'est jamais lue : le contrôle d'accès est en dur dans le Tcl. Le niveau qui y figure est documentaire — il ne commande rien.
PowerShell mange les quotes
Toute commande wsl -e bash -c "…" ou php -r "…" contenant des quotes, $( ) ou | arrive amputée, sans erreur. Écrire un script dans le scratchpad et le lancer par son chemin.
Deux familles de tables, deux collations
Les tables nées d'une migration .sql portent utf8mb4_unicode_ci ; celles nées du Tcl prennent celle du serveur. Comparer deux colonnes de collations différentes lève 1267 et arrête la migration à mi-chemin. Vérifier par une vraie jointure, jamais par « la table existe ».
CREATE TABLE IF NOT EXISTS ne migre rien
Il ne s'exécute pas sur une table déjà là. Tout changement de schéma demande AUSSI un ALTER, sinon il ne vaut que pour les installations neuves.
⚠ preg_split('/\R/') coupe les caractères accentués en deux
Trouvé le 13/08/2026, et il était partout : 17 occurrences dans 12 fichiers.
\R est la façon élégante d'écrire « n'importe quel saut de ligne ». Sans le modificateur u, PCRE travaille par octets, et \R y matche aussi l'octet 0x85 (NEL) — lequel apparaît au milieu de séquences UTF-8 multi-octets. Le découpage coupe donc un caractère en deux, et la chaîne recomposée n'est plus de l'UTF-8 valide.
⚠⚠ LA PANNE QUI S'ENSUIT EST MUETTE. Un preg_match_all portant u refuse une chaîne invalide en rendant false — pas une erreur, pas un avertissement. C'est ainsi que update_journal_depuis() a rendu un tableau vide, c'est-à-dire « aucune mise à jour disponible », pendant que le dépôt en publiait quatre-vingt-neuf. Le module qui répond aux installations tierces.
⚠ Et /\R/u n'est PAS la réponse, quoiqu'il corrige ce cas-là. Sur une entrée qui n'est pas de l'UTF-8 valide — un $_POST, la sortie d'une commande, un fichier de configuration d'origine inconnue — preg_split avec u rend false à son tour, et le ?: [] habituel transforme l'échec en tableau vide. On échangerait une panne muette contre une autre.
Ce qu'il faut écrire :
preg_split('/\r\n|\r|\n/', $texte)
Aucun des deux défauts : il fonctionne en mode octet comme en UTF-8, et ne peut pas couper un caractère — 0x0A et 0x0D n'apparaissent jamais comme octets de continuation, qui sont tous ≥ 0x80. Les 17 occurrences ont été converties.
⚠ Le contrôle, en une commande — il doit rendre ZÉRO ligne :
grep -rn "preg_split('/\\\\R/" Web/ IriX/ Tools/ Bootstrap/ | grep -vE ':[[:space:]]*(//|\*|#)'
Le second grep écarte les commentaires — dont celui d'update.php qui explique le piège en citant la forme fautive. Sans lui, le contrôle rend une ligne à l'état sain, et « une ligne, celle-là » est une réponse que le prochain lecteur devra vérifier à la main. Un contrôle dont la bonne réponse n'est pas zéro se dégrade en un an.
⚠ Éprouvé par mutation le 12/08/2026 : remettre /\R/ dans anope.php le fait rougir, et le retirer le fait taire. ⚠ La première tentative de cette épreuve n'avait rien modifié — le motif contenait des |, qui servaient aussi de délimiteur à sed. Le contrôle paraissait aveugle alors qu'il n'avait rien à voir : une mutation doit d'abord prouver qu'elle a changé le fichier.
✅ Le bot bannissait le serveur des services de leur propre salon — corrigé en 4.16.59
⚠⚠ CE POINT A ÉTÉ ÉCRIT FAUX, PAR MOI, DEUX HEURES PLUS TÔT LE MÊME JOUR. Il annonçait « aucune conséquence fonctionnelle » sur la foi des cinq refus NUMERIC 484 visibles au journal. La phrase n'était pas périmée : elle était fausse à l'écriture, parce qu'elle décrivait le symptôme sans avoir cherché ce qui l'accompagnait.
La cause, mesurée le 13/08 à 18:53 : flood-join 5:60 compte cinq entrées du même hôte en une minute. L'injection des services en fait exactement cinq, en moins d'une seconde, toutes sous l'hôte du serveur de services. Eggdrop conclut au flood — et réagit comme contre un vrai flood.
[13/08 18:35:57] Flood de JOIN de @IriX.overtchat.fr ! Interdiction.
[13/08 18:35:57] MODE #!Services! +b *!*@IriX.overtchat.fr
Le serveur refuse les kicks. Il ne refuse pas le ban. Le salon des services portait un ban contre le serveur des services, reposé à chaque démarrage : 27 floods, 25 refus, et le ban encore en place au moment du constat. Sans effet sur ceux qui sont déjà entrés — et fatal au premier qui devrait entrer ensuite.
⚠⚠ IL A FALLU LES TROIS JOURNAUX, et chacun n'en portait qu'un tiers : IriX-sql.log les cinq refus (le symptôme), IriX.log le +b (la conséquence, que personne n'avait vue), et journalctl la cause — le mot « Flood » n'était que là. Chercher dans un seul aurait donné trois enquêtes différentes, toutes incomplètes.
⚠ Les quatre pistes du carnet étaient toutes fausses, et pour la même raison : elles cherchaient dans le code du projet. Le kick et le ban sont émis par Eggdrop lui-même. Et les réglages de salon ne le disaient pas non plus — les cinq salons portent exactement les mêmes. Ce qui distingue celui des services, ce n'est pas sa configuration, c'est que nous y injectons cinq clients d'un coup. Le compteur mesurait un flood que nous produisions nous-mêmes.
✅ LE DÉSARMEMENT FONCTIONNE — constaté le 13/08 à 19:07. La trace instrumentée dit flood-join sur #!Services! — avant : « 0 0 » · après : « 0 0 », IriX.chan porte bien #!Services! flood-join 0:0 là où les quatre autres salons gardent 5:60, et plus aucun « Flood de JOIN » depuis.
⚠⚠ ET POURTANT LE BAN REVENAIT ENCORE. Ce n'était plus la même cause : il est mémorisé dans le userfile, et Eggdrop le rejoue à chaque entrée sur le salon.
::#!Services! bans
- *!*@IriX.overtchat.fr:1786646157:+1786638957:1786640826:IriX:join flood
Le fichier donne lui-même le motif — « join flood » — et les dates. Posé à 1786638957, soit 18:35:57, le dernier flood réel, avant le correctif. Il expire à 1786646157, soit 20:35:57 : exactement ban-time 120 minutes plus tard.
⚠⚠ J'AI CONCLU DEUX FOIS TROP VITE SUR CE POINT, DANS LES DEUX SENS. D'abord « aucune conséquence fonctionnelle » — faux, le ban est réel. Puis, l'ayant vu revenir alors que le compteur était désarmé, « flood-join n'était pas la cause » — faux aussi. La cause était la bonne dès le départ ; ce qui manquait, c'est que le ban ne se recrée pas à chaque démarrage : il persiste. Juger sur l'absence d'un message, plutôt que chercher où l'objet VIT, coûte un aller-retour complet.
⚠ Le userfile n'avait pas été lu en entier, et c'est là que tout était écrit. Les premières recherches y cherchaient un motif de ban ; il fallait simplement l'ouvrir — il fait 729 octets.
✅✅ CONSTATÉ FERMÉ le 13/08/2026 à 20:37, sur les trois journaux, qui concordent.
Le ban est parti tout seul, à la seconde attendue :
20:36:01 #!Services!: mode change '-b *!*@IriX.overtchat.fr' by IriX
Il portait 1786646157 comme échéance, soit 20:35:57. Retiré quatre secondes plus tard, au passage du balayage d'Eggdrop. Aucun geste manuel — et il ne fallait surtout pas en faire : le userfile est tenu en mémoire et réécrit à l'heure ronde, une édition à chaud aurait été perdue.
Et il ne revient pas :
| Contrôle | Depuis le correctif (19:07) |
|---|---|
« Flood de JOIN » dans journalctl | 0 |
+b *!*@IriX.overtchat.fr dans IriX.log | aucun après 19:07:46 |
NUMERIC 484 dans IriX-sql.log | 5, tous à 19:07:5x — donc du ban mémorisé, avant son expiration |
Le réglage, source indépendante (IriX.chan) : #!Services! porte flood-join 0:0, les quatre autres salons gardent 5:60. Le désarmement est donc bien ciblé sur le seul salon où nous injectons cinq clients d'un coup.
⚠ Ce point a coûté cher pour une raison à retenir : chacun des trois journaux n'en portait qu'un tiers — IriX-sql.log le symptôme, IriX.log la conséquence, journalctl la cause. Et il a fallu ouvrir le userfile, 729 octets, pour comprendre que le ban ne se recréait pas : il persistait.
| Pour fermer | ✅ FERMÉ. Rien ne reste. ⓘ La ligne de ban figurera encore dans le userfile jusqu'à la réécriture de 21:00 — le fichier datait de 20:00, quand le ban était encore valide. Ce n'est pas un reste, c'est un instantané périmé. |
|---|
⚠⚠ Trois schémas ne s'initialisent qu'à la main — et deux le font encore
Trouvé le 13/08/2026 en déployant une colonne qui n'est jamais arrivée en base. ::soc::start_system appelle sept initialisations de schéma au démarrage. Trois modules n'en faisaient pas partie — mandat, botreg, botnet —, atteints seulement par la commande d'administration init_database, que personne ne lance sur un réseau qui tourne.
Leurs schémas tiennent parce qu'ils ont été posés une fois, à la main. Toute colonne ajoutée ensuite reste lettre morte.
⚠⚠ Et le silence est total. CREATE TABLE IF NOT EXISTS ne se plaint de rien sur une table déjà là ; un ALTER qu'on n'atteint jamais ne dit rien non plus. Aucun journal, aucune erreur, aucun banc rouge — la migration était juste, son banc vert, et elle ne s'exécutait pas. Seul un SHOW COLUMNS après redémarrage l'a montrée.
⚠ La méthode qui a tranché : chercher qui appelle db_init, plutôt que relire ce qu'elle fait. Les trois premiers soupçons — la migration, le redémarrage, la garde db_column_exists — étaient tous faux. C'est le geste des cinq portes de l'identification : chercher qui ÉCRIT, pas qui semble écrire.
✅ mandat corrigé en 4.16.56, botreg et botnet en 4.16.58 — tous les db_init du projet sont désormais appelés au démarrage.
⚠ CE POINT DISAIT « LES DIX ». IL Y EN A NEUF — mesuré le 15/08/2026 en dérivant des sources : 9 proc ::soc::…::db_init définis, 9 appelés dans le corps de start_system (auth, auto, botnet, botreg, ia, mandat, svc, uline, ulines::auto). Le fond était juste, le chiffre non. Un compte écrit à la main ne survit pas à la séance qui l'écrit — c'est la règle que ce carnet applique partout ailleurs, et qu'il a enfreinte ici. Le compte se mesure :
grep -rhoP 'proc ::soc::[\w:]*::db_init' IriX/cmds/*.tcl | sort -u | wc -l
awk '/proc ::soc::start_system/,/^}/' IriX/cmds/IriX-sql.tcl | grep -oP '::soc::[\w:]*::db_init' | sort -u | wc -l
⚠ Et l'erreur ne s'était pas vue parce qu'elle était RASSURANTE : dix est plus que neuf, donc la phrase paraissait couvrir davantage. Un chiffre faux vers le haut ne déclenche aucun soupçon — c'est précisément pourquoi il faut le dériver et non le relire.
⚠ La correction de botreg et botnet est PRÉVENTIVE, et le dire compte. Leur schéma déclaré a été croisé avec la base le 13/08 : les sept tables concordent, aucune colonne ne manque. Rien n'a été réparé. Ce qui est fermé, c'est la porte par laquelle la prochaine colonne serait restée lettre morte.
✅ Et le contrôle est DÉRIVÉ, pas énuméré — test-appels.tcl découvre les db_init dans les sources et vérifie que chacun est appelé dans start_system. Un module neuf y entre tout seul. C'est le point qui compte : trois constats écrits à la main auraient gardé les trois modules connus, or c'est une énumération à la main qui les avait oubliés.
⚠ Le banc borne sa recherche au corps de start_system : le nom d'un db_init figure aussi dans la liste de do_init_database, et chercher dans tout le fichier l'aurait rendu vert pour la mauvaise raison — celle-là même qu'il interdit.
| Pour fermer | ✅ FERMÉ le 13/08/2026, RECONSTATÉ le 15/08. Les neuf db_init sont appelés au démarrage — 9 définis, 9 appelés, dérivés des sources et non recomptés à la main —, le croisement déclaré/réel ne montre aucun écart sur les sept tables de botreg et botnet, et un contrôle dérivé garde la porte pour les modules à venir. ⚠ Ce qui reste vrai et ne se ferme pas : un CREATE TABLE IF NOT EXISTS ne migre rien, donc toute colonne nouvelle demande encore son propre ALTER sous garde de db_column_exists. |
|---|
⚠ Un appel au niveau du fichier s'exécute AVANT la configuration
Trouvé le 12/08/2026, après 171 occurrences muettes depuis le 27/07.
IriX-sql.tcl source ses modules entre ses lignes 12 et 629, et n'appelle ::soc::load_config qu'à la 12149. Toute ligne écrite au niveau d'un module — c'est-à-dire hors d'un proc — s'exécute donc avant que la configuration existe. À ce moment :
$::soc::botnickest vide → une table${t}_usersdevient_users, sans préfixe ;- les identifiants SQL sont vides → MySQL retombe sur l'utilisateur système.
irix-register.tcl appelait ::soc::reg::init de cette façon. À chaque démarrage et à chaque rehash, trois lignes tombaient au journal :
SOC SQL ERROR: connexion échouée -> Access denied for user 'servus'@'localhost'
SOC SQL ERROR: tentative avec host= user= socket=(non défini)
SOC REG ERROR: table '_users' absente — lancer db_init d'abord
⚠⚠ ET C'ÉTAIT UN DOUBLON. L'appel légitime existait depuis toujours au bon endroit — IriX-sql.tcl:12159, après load_config ET après db_init, avec sa garde info commands. Le retrait n'a rien enlevé : il a supprimé la première des deux exécutions.
⚠ Le vrai coût n'était pas l'erreur, c'était l'accusation. Ces lignes désignaient la base et les droits MySQL, où il n'y avait qu'un appel mal placé. Un journal qui accuse un innocent coûte plus cher qu'un journal muet : on cherche la panne là où on la lit.
Comment le voir : une ligne en colonne 0 qui n'est ni proc, ni namespace, ni bind, ni set, ni putlog, ni variable, ni source. Le balayage des 23 modules n'en a trouvé que trois, dont deux légitimes — des utimer 8, donc différés, ce qui est précisément la parade.
Ce qui demande la présence de l'exploitant
- ✅✅
LA PHRASE SECRÈTE DES SAUVEGARDES— FAIT le 15/08/2026 au soir.
~/.config/overtchat/backup.pass n'est plus uniquement sur le disque qu'il sauvegarde. Les archives sont chiffrées en AES-256 et copiées sur la Freebox : elles étaient donc à l'abri d'une panne de disque et illisibles sans cette phrase — perdre le disque, c'était perdre les deux.
⚠ CONSTATÉ PAR UNE PREUVE FONCTIONNELLE, pas par une comparaison de chaînes : gpg a déchiffré une archive réelle avec la copie externe, saisie au clavier. C'est l'usage exact du jour où le disque aura brûlé. Une égalité de chaînes n'aurait prouvé que l'égalité ; ceci prouve que ça marche.
⚠ Constaté par SerVuS, et il ne peut pas en être autrement : la copie vit hors de cette machine, donc hors de toute mesure que je puisse faire. C'est la limite naturelle de ce point, et elle est saine.
⚠⚠ Ce que la vérification a coûté — trois faux départs, et ils instruisent. Ce passage était écrit #### … indenté de deux espaces, dans cet élément de liste : md_render() ancre les titres en colonne 0 (/^(#{1,4})\s+/) et ne les reconnaît pas indentés. Le visiteur lisait donc les quatre dièses en clair sur ?p=versions. Passé en gras le 25/08/2026 — le désindenter l'aurait sorti de la liste qui le porte.
- Une comparaison qui rendait « 0 », lue comme « la copie est vide ». Elle l'était : le
read -rsmasque totalement la frappe, si bien qu'un collage raté ne se voyait pas. ⚠ Un outil de saisie muet fait douter de la donnée au lieu de l'outil. - Le collage ne passait pas dans le terminal —
Ctrl+Vn'y colle pas. Deux tours perdus avant de le nommer. - La recopie manuelle était fausse, et pour une raison prévisible : 40 caractères aléatoires dont une barre et un symbole, plus les confusions
l/1/IetO/0.
📌 Ce qui a débloqué : cesser de comparer des chaînes et tester l'usage réel (gpg déchiffre-t-il ?), puis afficher le secret numéroté caractère par caractère (fold -w1 | nl) pour rendre la relecture non ambiguë.
⚠⚠ ET J'AI FAILLI ACCUSER LA COPIE À TORT. Avant de conclure qu'elle était fausse, il a fallu vérifier que le vrai secret déchiffrait cette archive — sans quoi le test lui-même aurait été en cause. Il déchiffre les sept archives, donc le test était valide. ⚠ Et un contrôle antérieur avait rendu « 0 occurrence », que j'avais lu comme un résultat : un échec de déchiffrement aurait donné le même 0. Vérifier l'instrument avant d'accuser la mesure.
- Sonder l'IPv6 depuis un réseau extérieur — depuis la machine, la connexion ne quitte pas l'hôte et ne prouve rien. ⚠ Ce n'est plus une précaution théorique depuis le 12/08/2026 : la distribution porte bien une IPv6 publique, le serveur IRC y écoute, et le pare-feu Windows laisse passer 6697. Adresse, ports à essayer et enjeu : point 13 ;
- Le panel Freebox — session ouverte par l'exploitant, analyse ensuite ;
- ✅
Constater— fait le 09/08/2026. Neuf connexions depuis un vrai navigateur, par le nom public et donc par Apache, ont laissé neuf lignes au journal du serveur : « WebSocket : adresse relayée par 127.0.0.1 adoptée — 192.168.1.254 ». La preuve d'août portait sur une connexion fabriquée à la main ; celle-ci vient d'un navigateur, ce qui était précisément le doute.X-Forwarded-Forsur un vrai visiteur du webchat${t}_onlinele confirme :host=192.168.1.254, et non127.0.0.1.
⚠ Une moitié reste ouverte, et elle tient au chemin réseau, pas au code. L'adresse adoptée est celle de la Freebox, pas celle du poste : venir par le nom public depuis le réseau local fait faire à la box un aller-retour avec traduction d'adresse, si bien qu'Apache voit la box. Le mécanisme est donc prouvé de bout en bout — mais une adresse publique distincte ne se verra qu'avec un visiteur venu vraiment d'Internet. Même angle mort que la sonde IPv6.
- Les mots de passe : partyline, liens de botnet, comptes. Jamais posés automatiquement.
⚠ Un point n’est retiré de cette liste qu’après avoir été constaté fermé, jamais après avoir été corrigé. Le 7 août 2026, cinq points s’y sont révélés périmés : le défaut annoncé n’existait plus, ou pas comme il était décrit.
Bilan par branche
Chaque branche regroupe ses correctifs. Les nombres sont comptés dans le journal, jamais rédigés après coup : ils additionnent ce que chaque version range déjà sous « Créations », « Bugs corrigés », « Évolutions »… Cliquez une branche pour lire son détail intégral.
4.45 1 version · 3 septembre 2026 non ventilé
4.45.0 — 3 septembre 2026
Trois versions d'OverScript publiées le même jour, et une demande de SerVuS tenue.
OverScript 0.6.9, 0.6.10 puis 0.6.11
- 0.6.9 — Linux annoncé au journal, et la plateforme affichée à côté du numéro.
- 0.6.10 — 🔴 la mise à jour marche enfin sous Linux. Un poste Linux voyait la mise à jour, téléchargeait quatre-vingt-dix mégaoctets d'installateur
.exe, puis s'entendait répondre que l'installation n'existe que sous Windows. Le client choisit désormais son paquet dans la cléplateformes: l'installateur sous Windows, le.debconfié au gestionnaire de paquets du bureau, l'AppImage déposée avec son chemin. ⚠ Les postes déjà en 0.6.9 ne reçoivent pas ce correctif tout seuls — leur premier passage est manuel, et les notes le disent. - 0.6.11 — le bouton « Ouvrir le site » ouvrait le navigateur sur le point de mise à jour, c'est-à-dire du JSON en texte brut. Signalé depuis le client lui-même, par le formulaire de l'onglet Aide : c'est le premier retour de ce canal qui aboutit à une version publiée.
Le site : chaque anomalie montre sa page
Seule la 404 était habillée. Un refus d'accès au panel rendait une ligne noire sur fond blanc, un fichier absent rendait seize octets, et un export de base qui échouait affichait le message de l'exception PDO — avec le SQL et des chemins de fichiers. Trois familles de pages désormais : le site public, le panel, et Apache (maintenance.html pour un arrêt voulu, indisponible.html pour un défaut subi). ⚠ Les outils gardent leur réponse sobre : le choix se fait sur Accept.
Ce que le balayage des liens a trouvé
29 pages, 37 adresses internes, 6 liens externes : aucun lien mort. Mais trois listes recopiées là où un inventaire existait — la clé plateformes, la liste des systèmes de la page Versions, et un libellé qui ne nommait pas son système sous une colonne « Système ». Chaque fois, les constats étaient verts : ils parcouraient ce qui est annoncé, jamais ce qui aurait dû l'être.
Et un défaut de la règle 5
L'installateur ne posait que maintenance.html. Une installation tierce aurait eu ses ErrorDocument 500/502/504 pointant sur un fichier absent, sans que rien ne le dise avant sa première panne.
4.44 1 version · 2 septembre 2026 non ventilé
4.44.0 — 2 septembre 2026
Une journée passée à mesurer ce que le projet AFFIRMAIT, et à corriger ce que la mesure a démenti.
Aucun de ces défauts n'a été trouvé en relisant du code. Tous l'ont été en demandant à la chose elle-même — au serveur, à la base, au processus — ce qu'elle faisait vraiment.
Ce qu'un visiteur lisait et qui était faux
⚠⚠ Les quatre chiffres de la page d'accueil, sous un en-tête qui proclamait depuis des mois « LES CHIFFRES DE CETTE PAGE SONT DÉRIVÉS, PLUS ÉCRITS » : douze onglets pour 30 routes, onze mille lignes de Tcl pour 31 930, neuf campagnes pour 19, et « une seule commande » au-dessus d'un bloc qui en montre quatre. Tous dérivés, avec une aide de cache — dériver coûte, et les deux fonctions voisines avaient déjà un cache pour cette raison.
📌 Le pire n'est pas l'écart : c'est qu'un visiteur qui en vérifie un cesse de croire les autres, y compris ceux qui sont justes.
La page « Télécharger » ne proposait pas le client de bureau — elle filtrait le seul type « module » sous un titre qui promettait tout le reste. Et ses deux téléchargements « différents » servaient le même octet : la page compare désormais les empreintes et dit ce qu'elle a mesuré.
Le nom du service de pseudonymes était gravé 21 fois dans 13 fichiers, alors que nick_service() existe pour l'éviter. ⚠⚠ chat.php se contredisait dans la même page : elle calculait la valeur, la sérialisait pour son Javascript avec un commentaire expliquant qu'il ne faut pas la graver — puis l'écrivait en dur deux fois dans son propre HTML. Le pire cas était le corps d'un courriel de réinitialisation, qu'on ne peut plus corriger après l'envoi.
La table des cinq services de cmd_services() était recopiée à la main. ⚠ Elle disait vrai, et c'est exactement ce qui rend le défaut durable : une table recopiée qui concorde ne se remarque pas.
Deux fois, c'est un banc ou un commentaire qui GARDAIT le défaut
Le zoom iOS du panel. Le commentaire de la feuille et test-vues.php affirmaient tous deux « pas de !important : ces champs tiennent leurs dimensions de la feuille, pas d'un attribut style ». Prémisse vraie, conclusion fausse — le danger était une règle plus spécifique de la même feuille. Le constat ne dit plus « aucun !important » : il mesure ce qui le rend superflu ou nécessaire.
La pastille de mise à jour menait à une page qui ne nomme jamais la mise à jour. ⚠⚠ Le point 32 avait fait CONVERGER les deux liens vers cette cible : la convergence était juste, la cible était fausse, et leur accord l'a rendue invisible deux semaines.
Ce que le serveur faisait vraiment
SVSMODE tombait dans le default du dispatcher S2S — et la table des exceptions portait la raison qui l'excusait : « l'effet revient en MODE ». Mesuré dans m_service.c : le verbe est rediffusé tel quel. Un SVSMODE +o posé par un service n'atteignait donc IriX sous aucune forme.
Le serveur annonçait un salon d'aide qui refuse ses nouveaux venus. HELP_CHANNEL était compilé à #aide, qui rend un 448. Toute installation omettant la directive l'annonçait à chaque connexion. Le défaut est passé à vide : nommer un salon suppose qu'il soit ouvert, ce qu'aucun code ne peut garantir. ⚠ Prend effet à la recompilation.
Config/unrealircd/salons.conf : plus rien ne le lit — mesuré sur trois chemins. ⚠⚠ Et il est périmé de deux entrées : un fichier qu'on ne lit plus mais qui décrit encore est plus trompeur qu'un fichier absent, il a l'air d'une source.
Le défaut que personne ne cherchait
Le point de départ était mince : deux colonnes vides dans <bot>_online. En cherchant pourquoi, les cinq pseudo-clients se sont révélés détruits et recréés chaque minute. reconcile les prenait pour des fantômes — ils n'arrivent par aucun UID entrant, donc user_nick ne les connaît pas.
Deux lectures à 45 s : IriX passe de 6336 à 6381 s ; OverNick retombe de 25 à 10. ⚠⚠ since MENTAIT — le panel annonçait « connecté depuis 10 s » pour des services présents depuis des heures. Une ligne présente et fausse est pire qu'une ligne absente : elle a l'air mesurée.
Constaté fermé après rechargement : since passe de 65 à 140 s, +75 exact.
Sept bancs neufs, et ce qu'ils ont coûté
test-svsmode (24) · test-champs-nommes (44) · test-comptes-services (8) · test-nom-du-service (8) · test-cmd-services (10) · test-salon-aide (11) · test-online-services (13) · essai-salons-webchat (7).
⚠⚠ Un banc a payé son écriture au premier lancement : il a attrapé une erreur introduite par la correction elle-même — un user_nick inexistant lu hors catch, à chaque cycle.
⚠ Et la mutation a trouvé ce qu'aucune relecture n'aurait vu : un constat qui comparait une valeur à elle-même ; deux contrôles de require qui lisaient une ligne commentée comme active ; deux blessures vertes parce que vhost contient la sous-chaîne host et que le jeu d'essai donnait la même valeur aux deux.
📌 Éprouver un plancher, c'est créer la situation qu'il garde — pas le retirer. Un garde-fou retiré ne rougit jamais, par définition.
4.43 1 version · 31 août 2026 3 créations 2 modifications 3 constats 3 erreurs
4.43.0 — 31 août 2026
Une porte publique en écriture, la première — et ce qu'il a fallu poser autour.
Une version d'essai d'OverScript part vers un Mac, chez quelqu'un qui n'a ni ce dépôt ni ce panel. Ses retours doivent pouvoir arriver.
⚠⚠ L'envoi direct par courriel depuis le client a été refusé, et pas par difficulté : il faudrait embarquer des identifiants SMTP dans un binaire distribué, c'est-à-dire les offrir à quiconque ouvre le paquet. C'est le raisonnement qui interdit déjà d'y mettre un jeton GitHub. Le client parle donc au site, qui ne lui demande aucun secret ; c'est le serveur qui pourra prévenir par courriel, puisqu'il peut détenir des identifiants sans les distribuer.
Créations
Web/inc/overscript-rapport.php— réception, stockage, relecture. La table se crée au premier besoin, motif decontact_table_ensure().Web/pages/overscript.php— l'onglet du panel, avec sa pastille de rapports neufs. ⚠ C'est la seule page du panel qui affiche du texte écrit hors du réseau : elle le dit, et l'échappe comme tout le reste.- La route publique
?p=overscript-rapport.
⚠⚠ Ce que cette route a de particulier
C'est la première route publique du site qui ÉCRIT sans session. Toutes les autres passent par , qui exige une session : un client Electron n'en a aucune et recevrait un 400 systématique.
📌 Le jeton anti-CSRF n'est pas « oublié », il est sans objet : il protège d'un site tiers qui ferait poster le navigateur d'une personne connectée, et il n'y a ici ni navigateur ni session. Le risque est l'inondation. Il est donc couvert par des barrières d'une autre nature :
| Plafond par adresse | 20 rapports par heure |
| Plafond global | 200 par heure |
| Corps de requête | borné à 64 Kio avant lecture |
| Champs | liste fermée, chacun tronqué |
⚠ Le journal du client n'est jamais accepté : il peut contenir les conversations.
Modifications
Web/site/versions-overscript.phpannonce l'essai macOS, et ne propose aucun fichier pour Mac — il n'y en a pas. Un encadré qui aurait l'air d'une offre ferait chercher ce qui n'existe pas.Markdown/MOBILE.mdouvre la réflexion sur une application mobile.
Erreurs
- ⚠ Une classe CSS inventée,
btn-on, est passée sous le radar :test-vues.phpignore les attributs contenant du PHP, et la mienne était dans une condition. Le banc est resté vert. Corrigée au motif du projet (badge accent/badge off) — le vocabulaire CSS du panel est clos. - ⚠ Ma route avait écarté
?p=overscriptde?p=nicksde plus de 2000 caractères, ce qu'un banc interdit pour qu'elle reste parmi les routes JSON sans session. Déplacée — plutôt que d'assouplir le banc pour me donner raison. - 🔴 Et cette entrée de journal a elle-même été écrite deux fois. La première a traversé un
bashnon protégé : tous les accents graves ont été exécutés, et ce qu'ils encadraient — chaque nom de fichier — a été remplacé par la sortie de la commande, c'est-à-dire par du vide. Le texte restait lisible, les noms avaient disparu. 📌 Un document ne se compose pas dans une chaîne qui passe par le shell.
Vérifié
- La porte éprouvée de bout en bout en service :
GETrefusé (405), corps illisible refusé (400), message vide refusé (400), rapport accepté et rangé en base avec toutes ses colonnes, table créée toute seule. test-routes.php,test-vues.php,test-panel-actions.phpettest-overscript.php: tous verts après ajout du talon de pastille.- Côté client : 536 constats statiques, 151 au rendu réel, et quatre blessures de mutation sur le module d'envoi, toutes détectées — dont une qui fait boucler le banc sans fin, ce qui a imposé un délai maximal à la passe de mutation elle-même.
4.42 1 version · 31 août 2026 3 créations 4 modifications 1 suppression 4 constats 2 avertissements
4.42.0 — 31 août 2026
Le code était juste. C'est son emploi qui manquait — pour la cinquième fois.
Trois défauts du point 56, tous de la même forme : une procédure correcte que plus personne n'appelait, et quelque chose qui donnait l'illusion que ça marchait. Aucun n'était une faute de codage.
Le sujet des salons n'était écrit que par la connexion cliente du bot, donc pour les seuls salons où il siège ; ailleurs, <bot>_channels.topic gardait sa vieille valeur — et c'est cette colonne que le site affiche. track_burst, le lecteur du burst, était devenue inatteignable : la boucle bloquante refaisait une partie de son travail, posait le pseudo et jamais l'ident, l'hôte, l'adresse ni le nom réel. Le 421, enfin, était lié deux fois, gardé ainsi par une raison écrite en français que personne n'avait mesurée — et qui était fausse.
⚠⚠ Dans les trois cas un banc existait et restait vert : il mesurait la chose, jamais son emploi. Les deux bancs posés ici éprouvent aussi le câblage — qui appelle, depuis où, avec quels arguments.
Créations
IriX/tests/test-topic-s2s.tcl— 41 constats, dix blessures de mutation toutes mordantes. ⚠⚠ Le constat central : les deux formes du verbe ne se distinguent pas par expression régulière. Au burst le troisième champ est le poseur, en service c'est le sujet, et un sujet comme « rendez-vous samedi 20 » satisfait le motif long. On compte les paramètres comme IRC les compte — au premier" :"—, exactement comme le daemon (parc >= 5,src/cmd/m_channel.c).IriX/tests/test-burst-clients.tcl— 22 constats, sept blessures toutes mordantes, dont l'ordreLINKED→fileeventqui est la cause même du défaut.::soc::topic_ecrire— le point d'écriture unique de la colonne.
Modifications
::soc::ulines::auto::on_topic_s2s— lit les deux formes, appelée depuis la boucle de burst et depuis une brancheTOPICneuve du dispatcher S2S. ⚠ Le burst n'enregistre aucun événement : il annonce l'état, pas un changement. Sans cette distinction, chaque montée de lien fabriquerait un faux « sujet changé » par salon.::soc::ulines::auto::track_burst— rebranchée sur la boucle bloquante, aveclabeleten_burst. La boucle ne refait plus le travail à la main.::soc::pub_topic— tronquait à la taille de la colonne ni ce qu'il écrit, ni ce qu'il envoie. Le sujet partait entier sur IRC (le serveur en accepte 360), l'UPDATEn'en gardait que 255, etdb_topic_checkrétablissait le texte court 30 s plus tard : le posteur voyait son sujet se raccourcir tout seul.::soc::chanq::_ecrire_topic— délègue au point d'écriture unique.
Suppressions
::soc::raw::on_421et son bind. ⚠⚠ La raison qui le gardait — « retirer l'un ou l'autre perd quelque chose » — avait été raisonnée, jamais mesurée :on_numericenregistre le texte entier du numérique, nom de la commande compris. Un banc l'exécute désormais pour de bon.
Décisions
- ⛔
AWAYne sera pas porté vers le chemin S2S. Personne ne lit<bot>_users.awayniaway_msg— ni Tcl, ni PHP —, et le serveur tient son propre état pour répondreRPL_AWAY. Le point se rouvrira si une vue les lit un jour. - ⛔ Les
MODE +bdu burst non plus. Ils annoncent des bans préexistants, pas un changement ;<bot>_bansest la liste des bans permanents du bot, pas un reflet du serveur. Aucune vue n'est périmée — c'est la différence de nature avecTOPIC, et c'est elle qui tranche.
Avertissements
- 🟡
extraire, la procédure que six bancs recopient pour sortir une proc du fichier, cherche les accolades par position et casse dès qu'une signature a deux arguments par défaut. Le symptôme est bruyant, donc sans mensonge silencieux ; la version appariée est danstest-burst-clients.tcl, les autres copies restent à aligner. - ⚠ Les correctifs de burst n'agissent qu'à l'établissement du lien : un
.rehashne suffit pas, il faut redémarreririx.service.
Vérifié
- Les deux bancs neufs, blessure par blessure — 17 mutations, toutes mordantes, dont les six de câblage.
- ⚠ La passe de mutation elle-même a été corrigée deux fois : un banc qui plante n'affiche aucun « ÉCHEC » et ressemble à un banc vert ; et la restauration doit vivre dans un
finally, faute de quoi un incident laisse le code blessé sur le disque — c'est arrivé. - ⚠⚠ Mesuré que
bancs.shne peut pas servir de référence sur le poste Windows : seize bancs y rougissent (sockets, encodage,phpabsent) qui sont verts sous WSL. La mesure valable est celle du clone WSL. - 128 bancs au vert, 5759 constats sous WSL, contre 126 / 5691 avant — l'écart concorde exactement avec ce qui a été ajouté.
La couverture du code mort, mesurée
Le point 56 tenait pour acquis que quatre procédures mortes laissaient un trou. La question utile n'était pas « sont-elles mortes ? » mais « leur travail est-il fait ailleurs, et INTÉGRALEMENT ? ». Mesuré :
- ✅
on_WEBIRCest couvert —session_openécrit déjà la bonneip, celle qu'adopter_ip_relayeea validée. Son SQL ne pourrait d'ailleurs rien toucher : il exigesocket_id=0, que plus aucun appelant ne produit. - 🟡
on_SVSKILL,on_KILL,on_SVSNICKsont partiellement couverts, et l'écart se voit au panel :_sessions.event_typevaut'quit'au lieu de'kill'— valeur de l'ENUM que plus aucun chemin vivant n'écrit, alors queconnexions.phpl'affiche en badge ;quit_msgarrive vide par le chemin S2S ; et l'identité du tueur n'est plus dans une colonne, si bien queactivite.phpperd la traçabilité des kills.
⚠⚠ Et une dépendance cachée qu'aucun banc ne garde : toute cette couverture tient à ce que le serveur émette KILL et QUIT pour la même victime. Il pose FLAGS_KILLED mais ne le lit nulle part, là où le hybrid d'origine s'en sert pour supprimer le QUIT. « Corriger » cet oubli laisserait les sessions des tués ouvertes indéfiniment. C'est le premier banc à écrire.
4.41 1 version · 31 août 2026 1 création
4.41.0 — 31 août 2026
Un banc qui n'empêche pas les trente procédures mortes — il empêche la trente-et-unième.
Le point 56 a montré qu'une trentaine de verbes sont liés à un dispatcher qui ne les recevra jamais. Les supprimer serait le mauvais geste : leur travail n'est fait par personne d'autre, et l'effacer rendrait le trou invisible. Ce qu'on peut faire tout de suite, c'est rendre impossible d'en ajouter un de plus sans s'en apercevoir.
Créations
IriX/tests/test-verbes-atteignables.tcl— 9 constats, six blessures de mutation, toutes mordantes. Tout verbe lié doit être classé : atteignable par un client, ou inatteignable avec sa raison écrite. 32 verbes sont ainsi nommés, chacun avec le pourquoi. ⚠ La liste est dérivée du fichier, jamais recopiée — une liste recopiée dans un banc mesure ce que le banc croit, pas ce que le code fait. ⚠ Une raison de moins de 25 caractères est refusée : sans cela la liste deviendrait un tapis. Ce garde-fou a d'abord attrapé les miennes, deux raisons qui disaient « idem ».
Corrections
on_006retiré, avec sa branche. Une branche duswitchappelant une procédure vide, pour un numérique absent des binds donc jamais reçu : deux mensonges superposés. ⚠ Aucune décision à prendre — le corps ne faisait rien. C'est le constat qui croise branches et binds qui l'a trouvé.
⚠ Laissé ouvert, délibérément
421est bindé deux fois, et les deux gestionnaires font des choses différentes : l'un nomme la commande inconnue, l'autre écrit une ligne d'événement. Chaque commande inconnue produit donc deux lignes de journal. ⚠ Retirer l'un ou l'autre perd quelque chose : c'est une décision, pas une correction. Le banc porte l'exception avec sa mesure, et un second doublon le ferait rougir.
4.40 1 version · 31 août 2026 1 constat
4.40.0 — 31 août 2026
on_SERVER n'était pas un cas isolé : c'est un motif, et il touche une trentaine de procédures.
Ce matin, une procédure jamais atteinte laissait trois colonnes vides. En demandant quelles autres sont dans le même cas — le geste que ce projet oublie toujours de faire après une réparation —, deux mécanismes distincts sont apparus.
⚠⚠ bind raw écoute la connexion CLIENTE du bot. IriX-binds.tcl y lie 42 verbes, dont une trentaine sont strictement serveur-à-serveur ou client→serveur : un client n'en reçoit jamais. Huit d'entre elles écrivent en base ou ferment des sessions — on_SQUIT, on_KILL, on_SVSKILL, on_CHGHOST, on_CHGIDENT, on_VHOST, on_WEBIRC, on_AWAY.
⚠⚠ Le burst est lu par une boucle bloquante, qui avale tout avant de poser le fileevent. Un verbe qui n'arrive que pendant le burst n'atteint jamais le dispatcher, même s'il y a une branche. Restent dans ce cas TOPIC, AWAY et les MODE #salon +b — ce dernier déjà mesuré en réel le 12/08 : « IriX_events n'a rien reçu, pas de ligne du tout ».
Corrections
SIDest traité, des deux côtés. Il cumulait les deux mécanismes : aucune branche dans leswitch— il tombait dans ledefault, journalisé « ignoré » — et une arrivée pendant le burst. Réparer un seul des deux n'aurait rien donné. C'est lui qui annonce tous les serveurs derrière le pair, avec leur SID et leur description. ⚠ Sa forme diffère deSERVER: le SID y est un vrai champ, le troisième, là oùSERVERn'en porte aucun. Ne pas transposer l'une sur l'autre — c'est l'erreur qu'on_SERVERfaisait déjà en lisant le nombre de sauts.
Éprouvé
IriX/tests/test-burst-serveur.tcl passe de 23 à 34 constats, cinq blessures de mutation, toutes mordantes. Trois constats neufs lisent la source du dispatcher : ils gardent l'EMPLOI, pas seulement la procédure — on_SERVER était parfaite et n'était appelée de nulle part.
Registre
- Point 56 ouvert : l'inventaire complet, les deux mécanismes, et ⚠⚠ deux bancs verts sur du code mort —
test-sessions.tclexécuteon_WEBIRCet vérifie un SQL qui ne s'exécutera jamais ;test-auth.tclgarde une révocation que ce chemin ne déclenche pas. ⚠ Fermer ce point n'est PAS « supprimer le code mort » : le retirer transformerait un trou existant en absence invisible. Il faut porter le travail vers le chemin S2S d'abord, un verbe à la fois.
4.39 1 version · 31 août 2026 1 création
4.39.0 — 31 août 2026
La page « Serveur IRC » ne montre plus un extrait de la configuration, mais ce que le serveur ANNONCE.
Demandé par SerVuS, qui trouvait l'affichage inutile. La raison de le retirer est plus forte que cela : overtchat.conf porte les mots de passe de lien. Les lignes password étaient bien masquées — mais n'afficher que trois familles de blocs est un pari sur ce qu'on ne montre pas. Le jour où un bloc s'ajoute plus haut, la fenêtre glisse, et aucun banc ne rougit.
⚠ Un bouton de téléchargement du fichier a été envisagé et écarté, y compris pour les serveurs d'essai : leurs configurations ont la même structure, et un fichier d'essai finit par contenir un vrai secret. Cette porte-là ne se referme pas.
⚠⚠ Et ce qu'un fichier DÉCLARE n'est pas ce qu'un serveur ANNONCE. C'est la doctrine de la page « Statut serveurs », et l'écart entre les deux est justement l'information qui vaut la peine d'être vue.
⚠ Pourquoi pas une sonde du panel
La tentation était d'ouvrir une connexion IRC depuis PHP. Non : un NICK/USER lancé depuis le panel apparaîtrait comme un vrai visiteur — dans le journal, dans le LUSERS, et à l'écran de tout le monde. Le projet a déjà payé une variante bénigne de cela, les erreurs TLS du journal venant du panel qui sondait ses propres ports.
IriX, lui, est déjà connecté en client : le 004 et le 005 lui arrivent de toute façon. Les ranger ne coûte pas une connexion de plus, et c'est de l'observé au sens strict — ce qu'un vrai client a vraiment reçu.
Créations
IriX/tests/test-annonce.tcl— 24 constats, six blessures de mutation, toutes mordantes. Les deux qui comptent portent sur ce qui doit être écarté : une ligne005finit par une phrase en anglais (:are supported by this server), et un découpage naïf ferait de « are », « supported » et « by » des capacités du serveur ; le premier champ, lui, est notre propre pseudonyme. ⚠ Une clef d'annonce doit avoir la forme d'un jeton : un serveur bavard ne doit pas pouvoir écrire ce qu'il veut dans une clef primaire.
Corrections
- ⚠⚠ Une troncature qui se voit.
valest unVARCHAR(255): au-delà, la base couperait seule et le panel afficherait une valeur incomplète avec l'aplomb d'une valeur complète. On coupe donc soi-même, en laissant une marque. ⚠ Elle est en ASCII pur, et c'est le banc qui l'a exigé : la marque était d'abord un « … », et elle rougissait — cette valeur traverse Tcl, MySQL et PHP, soit trois occasions de se transformer. Web/inc/config.example.php— le commentaire deircd_confdécrivait un repli surunreal_confavec le titre « Extrait de unrealircd.conf ». Ce repli n'existe plus. La clé reste : la page Admin en montre l'emplacement, jamais le contenu.
⚠ Ce que cela demande
La carte reste vide jusqu'à la prochaine connexion d'IriX — un .rehash ne suffit pas, il recharge les scripts sans reconnecter. La page le dit elle-même plutôt que d'afficher un vide muet.
4.38 1 version · 31 août 2026 1 création
4.38.0 — 31 août 2026
Trois colonnes que le panel affichait depuis toujours, et que personne n'avait jamais écrites.
Le tableau « Serveurs vus » montrait des tirets à la place du SID, du réseau et de la description. La cause n'était pas l'affichage : la seule procédure qui remplit ces colonnes n'est jamais atteinte, pour trois raisons qui se cumulent.
on_SERVERest branché parbind raw, donc sur la connexion cliente du bot — etSERVERest strictement serveur-à-serveur. Un client n'en reçoit jamais. Code mort sur ce chemin.- Sur le vrai chemin S2S, la ligne arrive pendant le burst, dans la boucle bloquante qui lit tout avant de passer en
LINKED. Ni le dispatcher nitrack_burstne la voient. ⚠⚠ C'est mot pour mot le piège que le commentaire d'on_burst_salondécrit déjà pour les salons : il avait été trouvé, réparé pour eux, et personne n'a regardé si d'autres verbes étaient logés à la même enseigne. - Et
on_SERVERlisait[lindex $parts 1]comme SID, alors que c'est le nombre de sauts. Il aurait écrit « 1 » — une colonne remplie de « 1 » étant bien pire qu'une colonne vide, parce qu'elle a l'air mesurée.
⚠ Un troisième code avait constaté le symptôme sans remonter à la cause : irix-online.tcl note que numeric_id « n'est renseignée par personne aujourd'hui » et retombe sur le SID brut.
Corrections
IriX/cmds/IriX-sql.tcl—::soc::ulines::auto::on_burst_serveur, appelée depuis la boucle de burst. ⚠ Le SID n'est pas dans la ligneSERVER: il vit dans unPROTOCTLqui arrive avant, et la version dans le quatrième champ d'EAUTH. Mesuré sur les lignes réelles du lien du 31/08 à 10:20:37, recopiées dans le banc.- Un commentaire menteur retiré au même endroit : « ils seront réactivés via RAW SERVER » promettait une réactivation qui n'arrivait jamais, si bien que tout serveur autre que celui du bot restait bloqué sur
online='non'après chaque reconnexion. - Une garde morte retirée :
if {$nom eq ""}ne pouvait pas se déclencher, l'expression régulière exigeant déjà un nom non vide. ⚠ C'est la passe de mutation qui l'a montrée — la blessure « on n'exige plus un nom » laissait le banc tout vert.
Créations
IriX/tests/test-burst-serveur.tcl— 23 constats, six blessures de mutation dont cinq mordent. Le constat central : sansPROTOCTL, le SID reste VIDE et ne vaut surtout pas « 1 », mesuré sansPROTOCTL— avec lui, il passerait pour une bonne raison qui n'est pas la bonne.
Publication
- OverScript
0.4.1est déposé sur le site. Il proposait encore0.2.0, publié le 26/08, et servait un binaire de 90 287 977 octets — une taille qui ne correspondait à aucun artefact encore présent dans ledist/du client. Web/tests/test-overscript.php— le trou qui rendait cela possible est refermé. Le numéro se recopie à la main (Config/overscript.jsonle dit : « le seul endroit du circuit qui ne dérive de rien ») et le binaire est servi sous un nom sans numéro : le site pouvait donc annoncer une version et en servir une autre indéfiniment, tous les bancs restant verts. Lelatest.ymld'electron-builder, déposé avec le binaire, porte le numéro et l'empreinte SHA-512 du paquet réel ; deux constats les croisent. ⚠ Ils ne jouent que sur la machine qui sert, et le disent quand ils ne jouent pas — un silence ressemblerait à une réussite.
4.37 1 version · 31 août 2026 1 création 1 constat
4.37.0 — 31 août 2026
Le serveur déclarait le service, le client écoutait le mauvais numéro — et aucun des deux dépôts n'avait de banc capable de le voir.
Signalé depuis le vrai réseau : OverScript refusait de s'identifier, en disant qu'OverNick « ne se déclare pas service réseau ». Son journal tient les deux lignes à trois secondes d'écart :
<< :serveur 320 SerVuS OverNick :is a Network Service
ATTENTION « overnick » ne se declare pas service reseau
Overtchat-Hybrid envoie le 320 (RPL_WHOISSPECIAL) ; le client n'écoutait que le 310. ⚠⚠ Et il acceptait ce 310 sans lire son texte, alors qu'il vaut « is available for help » sur cette même famille de serveurs : un simple volontaire à l'entraide aurait reçu le mot de passe. La garde était trop étroite et trop large à la fois.
⚠⚠ Le contrat n'avait de banc dans aucun des deux dépôts. Les 498 constats du client et les 18 campagnes du serveur étaient verts pendant la panne : chacun mesurait son côté, et la valeur sur le fil ne concordait pas. Pire, le faux serveur du client était plus généreux que le vrai — un 310 que nul n'émet, posé sur services.exemple.fr, un nom qui suffisait à lui seul. Deux indices forts là où la production n'en offrait qu'un, et le mauvais.
📌 Question à poser dès qu'une valeur voyage entre deux dépôts : lequel des deux bancs rougirait si l'autre côté changeait ? Si la réponse est « aucun », la valeur n'est pas gardée.
Corrections
Servers/overtchat-hybrid/tests/harness.py— le constat « il est signalé comme service » ne cherchait que le texteNetwork Service. Il fige désormais le numérique 320 autant que le texte : déplacer ce numéro casserait tous les clients sans rougir.
Créations
IriX/tests/test-edition.tcl— 27 constats. Le lecteur d'édition Tcl était le seul des trois langages sans banc, et::soc::edition_au_moinsn'avait aucun appelant, y compris dans les bancs — l'état exact où se trouvait son homologue bash la veille. ⚠ Le lecteur reste mort ; il est désormais mort et mesuré. Cela n'ouvre pas la phase 3 du point 9. Les deux constats qui portent le banc : une édition demandée inconnue refuse, mesurée sur l'installation la plus complète — c'est le défaut derequire_grade, où unlsearchrendant-1comparé par>=ouvre au lieu de refuser ; et les deux gardes contre les notes_editionet_bots_chan, qui citent en toutes lettres les valeurs cherchées.
Documentation
Markdown/PROTOCOLES.md— le fait « ceci est un service » s'annonce par trois numériques selon qui répond :320(le serveur),313(IriX sur un WHOIS distant),310(d'autres ircd). Aucun des trois ne se lit sans son texte. Et le serveur ne pose jamais+Sdepuis le statut U-line : c'est IriX qui l'annonce dans sesUID, donc retirer ceScouperait l'identification de tous les clients sans qu'un banc du serveur ne bouge.Markdown/CHANTIERS.md,Markdown/EDITIONS.md— les trois lecteurs d'édition ont un banc ; ⚠ deux n'ont toujours aucun appelant. Un banc qui mesure la chose ne mesure pas son emploi.
Éprouvé
Les 18 campagnes du serveur, la suite complète du dépôt, et — hors dépôt — OverScript en 0.4.1 puis les 504 constats de sa suite. Mutation des deux côtés : n'écouter que le 310 rougit le constat du 320 ; cesser de lire le texte rougit le helpop et le robot.
4.36 1 version · 30 août 2026 1 création 1 constat 3 avertissements
4.36.0 — 30 août 2026
edition_au_moins() n'avait aucun appelant — et c'est ce qui rendait la phase 3 du point 9 dangereuse.
Mesuré : Tools/edition.sh n'est sourcé nulle part hors test-edition.php, qui n'exerce que edition_lire. Les autres lignes du dépôt qui nomment ce fichier le livrent (manifeste, catalogue) sans jamais l'exécuter. edition_au_moins(), elle, n'était appelée par rien du tout.
Ce n'est pas de l'hygiène : la phase 3 consiste à brancher ce lecteur dans overtchat-setup.sh, 12 509 lignes. Le registre dit d'elle qu'elle « branche un lecteur mort », et c'est pour cela qu'elle attend. ⚠ Ce banc n'ouvre pas la phase 3 — il retire un argument, pas un obstacle.
Créations
Tools/tests/test-edition-sh.sh— 27 constats, dont les quatorze comparaisons de rang que rien n'exerçait. ⚠ Tout se joue dans un bac à sable : un banc qui écrirait dansConfig/config.jsonpour s'éprouver changerait l'édition de l'installation le temps de son passage, et la laisserait changée s'il était interrompu. Un constat vérifie que la configuration réelle n'a pas bougé.
Éprouvé
5 mutations, toutes attrapées, et chacune sur les constats qui la visent :
| Blessure | Ce qui rougit |
|---|---|
| le rang devient une égalité stricte | les 5 cas « au moins une édition inférieure » |
> au lieu de >= | les 4 cas d'égalité, écrits exprès pour elle |
| une édition demandée inconnue OUVRE | le constat du défaut prudent inverse |
le défaut prudent ouvre (beta → payante) | les 2 constats de lecture |
| le banc n'écrit plus dans son bac à sable | 9 constats — le montage lui-même |
Avertissements
- ⚠ Trois affirmations du registre étaient périmées, et sont corrigées :
Web/tests/test-compat.phpn'éprouve pas les quatre éditions — il ne contient pas une fois le mot « édition » ; l'installateur fait 12 509 lignes et non 10 429 ; il porte 353 définitions de fonction et non 333. 📌 Les deux chiffres avaient été annoncés « mesurés » — ils se sont périmés à leur tour. Un compte écrit à la main ne survit pas à la séance. - ⚠ Le même trou existe en Tcl :
::soc::edition_au_moins(IriX/cmds/irix-edition.tcl) n'a aucun appelant non plus, y compris dans les bancs. Sur les trois lecteurs, seul le PHP a un appelant en production (Web/inc/compat.php). - ⚠ Aucune phase 3 à 8 ne se ferme sans un geste ou une décision de SerVuS — mesuré phase par phase. La 6 est la seule dont le bloqueur n'est pas un geste mais une décision produit : quelle page appartient à quelle édition.
4.35 1 version · 30 août 2026 2 bugs corrigés 1 constat 3 avertissements
4.35.0 — 30 août 2026
Le troisième privilège mort — et un raisonnement de confidentialité qui a cessé d'être vrai sans le dire.
OPRIV_SEE_REALHOST est lu aux trois endroits qui montrent l'hôte réel : WHOIS (378), USERHOST (302) et TRACE. Les deux pièges annoncés en 4.32.0 étaient réels, et tous deux traités.
Bugs corrigés
- ⚠⚠ Le
ifde WHOIS mêlait deux révélations de nature différente : l'hôte réel — ce que le privilège nomme — et les modes de la cible, qui ne relèvent d'aucun privilège. Y greffer la garde sans scinder aurait retiré aux opérateurs quelque chose que personne n'avait décidé de leur retirer. 📌 Une condition partagée par deux effets n'est plus une condition : c'est une coïncidence, et elle se paie au premier qui veut n'en régler qu'un. - ⚠⚠ TRACE serait devenu le contournement. Son commentaire assumait d'afficher l'hôte réel : « c'est ce qu'un opérateur vient chercher, et il l'obtient déjà par WHOIS ». Le raisonnement était juste — tant que WHOIS le donnait à tout opérateur. Il est mort à l'instant où WHOIS s'est resserré, et rien dans le code ne l'aurait dit. 📌 Un raisonnement de confidentialité qui s'appuie sur « on l'obtient déjà ailleurs » meurt le jour où « ailleurs » se resserre.
Éprouvé
60 constats, 4 mutations toutes attrapées — dont celle qui refusionne le if de WHOIS (seul le constat de la scission rougit) et celle qui rouvre TRACE.
⚠⚠ DEUX DE CES CONSTATS NE POUVAIENT PAS ROUGIR, et c'est la mutation qui l'a dit. Ils comparaient les réponses entières reçues par les deux opérateurs — or un numérique porte le nom de son destinataire : « 205 Mod … » contre « 205 Oper … ». Les lignes différaient toujours, quel que soit l'hôte montré. La mutation qui rouvrait TRACE est restée verte. Les constats extraient désormais l'hôte du masque avant de comparer. 📌 Comparer deux réponses entre elles ne mesure rien tant qu'on n'a pas ôté ce qui diffère pour une autre raison que celle qu'on éprouve.
Avertissements
- ⚠ Ce resserrage touche les grades
ircopetmoderateur, qui ne portent passee-realhost— même doctrine que pouroverrideetsee-secret. - ⚠ Il reste deux bits morts (
chghost,admin) et un à ne jamais brancher (svs-commands). Le raisonnement de chacun est dansinclude/conf.h. - ⚠⚠ Toujours pas en service : cela attend la recompilation du point 55.
4.34 1 version · 30 août 2026 3 modifications 2 bugs corrigés 1 constat 3 avertissements
4.34.0 — 30 août 2026
Trois planificateurs ne retiraient rien avant de poser — et le registre disait qu'il n'y en avait qu'un.
Le point 36bis annonçait six boucles à traiter et un seul planificateur fautif. La mesure a retourné les deux moitiés : les six sont traitées depuis le 17/08, et les fautifs étaient trois. Ce sont exactement les trois qui posent par after — la file de Tcl, sur laquelle ni killtimer ni killutimer n'ont la moindre prise.
Bugs corrigés
- ⚠⚠
chatalerte::schedule,raw_log_purge_schedule,topic_schedulereposaient sans rien retirer. Chaque.rehashleur ajoutait une chaîne parallèle :system_readyettopic_startedvivent dansIriX-src.conf, que le re-source remet à vide, donc la garde d'amorçage rouvre — et l'ancienne chaîne n'est pas annulée, personne n'ayant son identifiant. Pourchatalerte, deux chaînes qui se croisent annoncent deux fois la même alerte. ⚠raw_log_purge_schedulea deux sites d'amorçage : il accumulait le plus vite, et le registre ne le mentionnait pas. - ⚠ Deux
killutimerétaient inopérants sans le dire (irix-oper.tcl,modes_du_bot) : ils passaient un nom de procédure là où un identifiant de minuteur est attendu. L'appel levait, lecatchavalait, le minuteur survivait. 📌 Uncatchautour d'un appel faux le rend indistinguable d'un appel juste.
Modifications
IriX/cmds/IriX-sql.tcl—::soc::after_remplacer, une seule procédure qui balaie la file réelle avant de poser. ⚠ Elle ne garde pas d'identifiant en variable, et c'est la leçon déjà payée parrehash_schedule: une variable vit dansIriX-src.conf, que le rechargement réinitialise — l'identifiant est perdu, le minuteur devient orphelin et intuable. La file, elle, ne ment pas.IriX/cmds/irix-oper.tcl— le balayage y est écrit en ligne, à dessein : ces lignes s'exécutent au CHARGEMENT du fichier, sourcé bien avant que la procédure partagée n'existe. Un appel y serait la « proc inexistante ».IriX/tests/test-minuteurs.tcl— règle C, qui manquait entièrement.
Éprouvé
12 constats, 5 mutations toutes attrapées — les trois planificateurs blessés un par un, l'amorçage du cycle des sujets, et le détecteur rendu aveugle.
⚠⚠ Le banc ignorait la troisième file. Eggdrop en tient deux, Tcl une troisième, et la liste de planificateurs de ce banc exigeait un crochet devant timer : after $DELAI … n'y entrait pas. Les trois fautifs lui étaient invisibles — et à test-boucles.tcl aussi, qui garde le défaut symétrique (une boucle ne doit pas MOURIR ; celles-ci se MULTIPLIAIENT).
Avertissements
- ⚠ La règle neuve est étroite, et ça s'est mesuré avant de l'écrire. « Toute procédure qui pose un
afterdoit retirer » dénonçait onze procédures justes — des délais à un coup, qui n'ont rien à retirer. Seul l'auto-réarmement est gardé. 📌 Un banc qui accuse onze innocents pour deux coupables finit ignoré, et c'est le coupable qui gagne. - ⚠⚠ La règle a été VERTE À VIDE à sa première exécution. En corrigeant les trois planificateurs, on leur avait retiré le
afterlittéral qu'elle cherchait : elle ne voyait plus aucune boucle, et le constat portait sur un ensemble vide. C'est le plancher qui l'a dit, pas le constat. 📌 Une règle écrite sur la FORME fautive meurt avec elle — il faut qu'elle reconnaisse aussi la forme corrigée. - ⚠⚠ Rien n'est en service. IriX charge ces fichiers au démarrage : il faut un
.rehashou un redémarrage, donc l'accord de SerVuS. 📌 Un.rehashsuffit et se répare seul — le code neuf balaiera les chaînes anciennes au premier cycle.
4.33 1 version · 30 août 2026 3 bugs corrigés 1 constat 3 avertissements
4.33.0 — 30 août 2026
Trois menus de l'installateur dessinaient hors cadre — et « hors cadre » était littéral.
Point 48, morceau (a). La piste attendue était un débordement de largeur ; la mesure a montré autre chose. Ces trois écrans n'ouvraient aucun cadre du tout : ils écrivaient leurs entrées par cat <<-MENU, le bandeau se fermait proprement, et les lignes flottaient en dessous.
Bugs corrigés
- ⚠
_eggdrop_grades_menu,_panel_service_detail,_panel_server_detaildessinent maintenant comme les douze autres écrans. ⚠ Ce n'était pas une affaire de largeur : la plus longue entrée fait 59 colonnes pour un cadre d'au moins 64, sans couleur ni caractère large — rien à mal compter. - ⚠⚠ Deux aggravants non répertoriés : les deux panels de détail appelaient
_services_overview/_servers_overview, dont le contrat écrit exige « un cadre déjà ouvert par l'appelant », sans en ouvrir. Ils produisaient des bordures│ … │et un├───┤sans toit ni plancher, suivis de_kvhérités sans bordure du tout : trois dialectes empilés dans un écran. - ⚠
Bootstrap/tests/test-ui-cadre.shétait plus fragile que le code qu'il éprouve : il n'extrayait pas_ui_localeet rougissait 24/36 dans un shell sansLANGUTF-8,${#s}comptant alors des octets. Le script réel, lui, appelle_ui_localeà son chargement. Le banc pose désormais sa locale. 📌 Un banc qui rougit selon l'environnement n'accuse pas le code : il apprend à ne plus être cru.
Éprouvé
39 constats, verts avec et sans locale, dont trois neufs, dérivés et non énumérés — un menu ajouté demain avec un heredoc rougira sans que personne pense à l'inscrire : tout écran de menu ouvre un cadre · le plancher qui empêche le vert silencieux · plus aucun heredoc de menu.
Deux mutations, toutes deux attrapées : remettre un heredoc dans un menu (les deux constats de forme rougissent) ; faire rendre le découpage en fonctions à vide (seul le plancher rougit — c'est exactement son rôle).
Avertissements
- ⚠⚠ Ce banc était vert pendant tout ce temps, et il ne mentait pas. Il prouve qu'un cadre ouvert se referme à la même colonne ; ces trois-là n'en ouvraient jamais, donc l'invariant était vrai chez eux et vide. Il filtre par ailleurs sur
_menu_add/_menu_gate, que ces menus n'emploient pas. 📌 Ce qu'un banc ne regarde pas ne devient visible qu'en changeant la QUESTION, jamais en durcissant la mesure existante. - ⚠ Le constat neuf a rougi à sa première exécution — sur un COMMENTAIRE. Celui qui venait d'être écrit pour expliquer le défaut retiré, et qui en cite la forme. Les lignes de commentaire sont désormais écartées. Une phrase qui documente un défaut corrigé ressemble exactement au défaut.
- ⚠ Le morceau (b) du point 48 — l'option
--web— reste fermé : il attend trois décisions non techniques, dont unphp -Slancé en root et atteignable par tout utilisateur local.
4.32 1 version · 30 août 2026 1 bug corrigé 1 constat 4 avertissements
4.32.0 — 30 août 2026
Un second privilège mort, et la mesure de ce qu'il ne faut SURTOUT pas brancher.
La 4.30.0 signalait « six privilèges sur vingt et un ne sont lus nulle part ». Les avoir mesurés un par un a retourné ce constat : ils ne sont pas six cas du même défaut, mais quatre cas différents, dont un où brancher le privilège serait une panne totale.
Bugs corrigés
- ⚠⚠
OPRIV_SEE_SECRETn'était lu nulle part. Il gouverne désormais les cinq endroits qui montrent ce qui est caché :NAMES,LIST,WHO, les salons rendus parWHOIS, et l'affichage de la clé+k— voir la clé, c'est pouvoir entrer, et c'est le site qui compte le plus des cinq. ⚠ Ce resserrage corrige une contradiction : le grademoderateurdeTools/opers-sync.shporte en commentaire « rien pour voir ce qui est caché », et voyait pourtant tout. Les quatre autres grades portentsee-secretet sont inchangés. ⚠ La forme de chaque garde est conservée telle quelle : elle diffère d'un site à l'autre (NAMES/WHOadmettent l'opérateur local,LIST/WHOISexigent+o). Cette asymétrie est antérieure et n'a pas été mesurée — l'uniformiser au passage aurait changé deux choses à la fois.
Éprouvé
Deux mutations, symétriques et toutes deux attrapées : le privilège jamais lu — les quatre constats de refus rougissent ; personne ne voit plus rien — les quatre témoins rougissent. Les cinq sites sont blessés à chaque fois.
⚠⚠ UN FAUX VERT A ÉTÉ PRIS EN CHEMIN, et c'est le fait le plus utile de cette version. Le constat « il n'énumère pas les membres du salon caché » passait au vert sans mesurer le privilège : channel_send_names écarte aussi les utilisateurs invisibles pour les non-membres, et le serveur pose +i à l'enregistrement. Le seul occupant du salon d'essai était donc invisible — le silence observé avait deux causes possibles, et la seule qu'on croyait mesurer n'était pas celle qui agissait. Un MODE Chef -i écarte la seconde. 📌 Tant qu'une seconde cause peut produire le même silence, on ne mesure pas la première. Le constat rougit maintenant sous mutation — avant, non.
Avertissements
- ⛔
OPRIV_SVSCMDSNE DOIT PAS ÊTRE BRANCHÉ sur le modèle des deux précédents : ce serait une panne totale des services. Les commandes SVS* n'ont qu'un créneau serveur, inaccessible à tout opérateur client, et leur vrai rempart estMFLG_ULINE+ulines{}— une autorisation par identité de serveur, strictement plus sûre. IriX et Anope passent en clients U-linés : ils ne portent jamaisUMODE_OPERet n'ont aucun bloc oper. Une garde calquée suroverriderejetterait 100 % de leur trafic. 📌 Ce bit n'est pas un oubli : son modèle d'autorisation a été remplacé par un meilleur. Le raisonnement complet est écrit dansinclude/conf.h, à côté du#define— c'est là que le lira celui qui se posera la question. - ⚠
OPRIV_CHGHOSTetOPRIV_ADMINsont des bits qui MENTENT dans un/STATS o: le premier garderait une commande qui n'existe pas pour un opérateur, le second n'a aucun référent (la commandeADMINest publique, il n'y a pas d'UMODE_ADMIN, et le pouvoir est déjà décomposé enDIE,RESTART,REHASH,SQUIT,CONNECT). Décision à prendre : leur donner un objet, ou les retirer. Ce ne sont pas des correctifs. - ⚠
OPRIV_SEE_REALHOSTse branchera, mais pas d'un seul&&: leifdeWHOISrévèle l'hôte réel et les umodes dans la même condition, etTRACEaffiche l'hôte réel sans aucune garde — il deviendrait le contournement. Il restreindrait aussi deux grades. - ⚠⚠ Toujours pas en service : comme les 4.30.0 et 4.31.0, cela attend la recompilation du point 55.
4.31 1 version · 30 août 2026 4 modifications 2 bugs corrigés 1 constat 3 avertissements
4.31.0 — 30 août 2026
Le serveur disait « une exemption silencieuse est un piège », et il n'en disait qu'une sur six.
Le privilège rendu lisible en 4.30.0 a mis en évidence la moitié manquante du dispositif : MODE était le seul acte d'outrepassement annoncé aux autres opérateurs. Entrer sur un salon fermé, y parler malgré un ban, expulser, poser le sujet, inviter — personne n'en savait rien.
Bugs corrigés
- ⚠⚠ Cinq actes en force n'étaient annoncés à personne. Ils le sont désormais sur le masque
o(SNO_OPERATEURS) : l'entrée (+b,+i,+k,+l,+J), la parole (+m, ban, mute),KICK,TOPIC,INVITE. ⚠sendto_realopsécrit aussi au journal — un seul appel couvre les deux besoins, et unilogde plus ferait une entrée en double. - ⚠⚠ L'annonce décrivait un acte qui pouvait ne pas avoir lieu. La notice privée partait dès le passe-droit accordé, mais
+j(limitation d'arrivées) et+Mrefusent encore après : l'opérateur lisait « votre statut vous en dispense », puis se voyait refuser. Les deux envois sont déplacés après la dernière garde. Défaut préexistant, sans rapport avec l'extension — il s'est vu en cherchant où poser l'avis nouveau. 📌 Un message vrai au moment où on l'écrit peut devenir faux deux gardes plus bas : ce qui compte n'est pas ce qu'on sait, c'est ce qui va se produire.
Modifications
src/channel.c— annonce à l'entrée et à la parole, l'une et l'autre au point où l'acte est acquis.src/cmd/m_channel.c— annonce surKICK,TOPIC,INVITE. ⚠ PourKICK, trois gardes peuvent céder pour une seule expulsion : un drapeau les rassemble et l'avis part une fois, après+Qetprotection_shields(), qui refusent encore plus bas.conf/overtchat.conf.example—OPER_OVERRIDE_NOTIFYétait posé sans une ligne d'explication. Il en a maintenant, dont celle qui compte : le mettre à « no » ne retire aucun droit, il rend seulement les passages en force invisibles.tests/test-oper-outrepasse.py— un veilleur (opérateur portant+s), et les constats des six actes.
Éprouvé
⚠ Aucun compte n'est écrit ici : python3 tests/test-oper-outrepasse.py et ./Tools/bancs.sh les donnent. Quatre mutations, toutes attrapées :
| Blessure | Ce qui rougit |
|---|---|
| l'avis d'entrée retiré | les trois constats qui en dépendent |
| on annonce aussi les actes de plein droit | le seul garde-fou du faux signalement |
la notice postée avant la garde +j — l'état d'avant | « aucune dispense ne lui a été annoncée » |
| l'avis de parole retiré | le constat correspondant |
⚠⚠ La deuxième est la plus importante des quatre. Un serveur qui signalerait tout acte d'un opérateur passerait les cinq constats d'annonce sans rien mesurer — et noierait les vrais avis sous le bruit des actes légitimes, ce qui revient à ne plus rien signaler. Le constat qui l'attrape est le seul de la campagne à vérifier une absence.
Avertissements
- ⚠ Sur la parole, l'avis part à chaque ligne, sans compteur. Repesé puis retenu : un état « déjà signalé » n'existerait que pour les membres, laisserait le salon sans
+nsans réponse, et taire la deuxième ligne d'un abus est exactement ce qu'on ne veut pas. La soupape estOPER_OVERRIDE_NOTIFY. - ⚠ L'avis ne nomme pas laquelle des trois gardes de
KICKa cédé : en nommer une quand deux ont cédé sous-dirait, et les nommer toutes demanderait d'assembler une phrase pour un détail que personne ne relira. - ⚠⚠ Toujours pas en service. Comme la 4.30.0, cela attend la recompilation du point 55, et partira dans le même geste.
4.30 1 version · 30 août 2026 4 modifications 4 bugs corrigés 1 constat 3 avertissements
4.30.0 — 30 août 2026
Un privilège se configurait sans jamais rien produire — et le lanceur de bancs mesurait le binaire de la veille.
La séance a commencé par un banc rouge : la campagne du barème annonçait « aucun jeton dans l'accueil » alors que le code qui l'émet était au dépôt depuis la veille au soir. ⚠ Le code était juste ; c'est le binaire d'essai qui datait de quatre heures plus tôt.
Bugs corrigés
- ⚠⚠
tests/run-tests.shne vérifiait que l'EXISTENCE du binaire d'essai, jamais son âge. Trois sources du point 55 lui étaient plus récentes, et les dix-huit campagnes mesuraient donc le serveur d'avant. Le sens inverse est le vrai danger, et il est silencieux : une régression écrite danssrc/ne se voit pas tant qu'on ne recompile pas, et tout reste vert. Le lanceur refuse désormais, en nommant les fichiers coupables — il ne compile pas lui-même : un lanceur qui construit en douce efface justement l'écart qu'on veut voir. - ⚠⚠
OPRIV_OVERRIDEn'était lu nulle part. Défini dansinclude/conf.h, accepté dans les blocs oper, documenté dansovertchat.conf.example, distribué par grade parTools/opers-sync.sh— et rien dans le serveur ne le consultait. Tout opérateur passait outre, que le privilège lui ait été accordé ou non ; le lui retirer ne lui retirait rien. Il est désormais lu aux huit endroits qui accordent un passe-droit de salon :oper_passe_outre()desrc/channel.c(JOIN et PRIVMSG, donc+b,+i,+k,+l,+m,+Jet le mute), puis MODE, KICK ×3, TOPIC et INVITE danssrc/cmd/m_channel.c. ⚠ Ce que cela change en exploitation : les gradesglobal,ircopetmoderateurne demandent pasoverride— ils passaient outre quand même, désormais non. C'est ce que ces grades disent depuis toujours ; le serveur ne les lisait pas.netadmin(« all ») etadminsont inchangés. - ⚠
INVEXétait annoncé deux fois dans le005— la forme nue en plus d'INVEX=I. La nue est retirée : un client qui range les jetons dans un sac gardait le dernier vu, donc celui sans valeur, et en déduisait la lettre au lieu de la lire. - ⚠ Le commentaire de
chanmode_req_symbol()illustrait le barème sous sa forme abandonnée (q:iqa), quatre-vingts lignes sous la table qui le contredisait. L'exemple est retiré plutôt que corrigé : un barème recopié à la main diverge, et c'est le défaut même que ce jeton existe pour corriger chez les clients.
Modifications
Servers/overtchat-hybrid/src/conf.c—oper_has_priv(), qui LIT un privilège sans parler à personne. ⚠ Elle existait déjà sous le nomhas_priv()dansm_oper.c, maisstatic, hors de tout en-tête, et envoyant un numérique au client en même temps qu'elle répondait : le code des salons n'avait donc queIsAnyOper()sous la main. Un service rendu inaccessible finit par être réécrit de travers, ou pas du tout.Servers/overtchat-hybrid/include/conf.h,src/channel.c,src/cmd/m_channel.c— la déclaration, et le passe-droit ramené à un seul endroit par fichier (oper_passe_outre,oper_outrepasse) plutôt qu'à huit copies d'une même règle.Servers/overtchat-hybrid/tests/harness.py— constat neuf : aucun jeton du005annoncé deux fois. Le doublon avait survécu parce que rien ne lisait l'accueil dans son ENSEMBLE, seulement trois jetons interrogés par leur nom.Servers/overtchat-hybrid/tests/test-oper-outrepasse.py— un second opérateur, identique au premier à un privilège près, et dix constats.
Éprouvé
18 campagnes vertes, 32 constats pour la campagne 17 (10 neufs), et 5 mutations rejouées, toutes attrapées :
| Blessure | Ce qui rougit |
|---|---|
| le privilège n'est jamais lu — l'état d'avant | 6 constats, tous les refus neufs |
| plus personne ne passe outre | 10 constats, dont les témoins positifs |
le doublon INVEX remis | le constat du 005, qui nomme le jeton |
le constat du 005 ne lit aucune ligne | il rougit (« 0 jeton lu ») |
| une source plus récente que le binaire | le lanceur refuse et nomme le fichier |
⚠ Les deux dernières comptent autant que les trois autres. La quatrième éprouve que le constat neuf peut rougir : sans la seconde moitié de sa condition, il aurait passé au vert en ne lisant rien — zéro doublon parmi zéro jeton. Et la seconde éprouve le défaut symétrique : un banc qui ne vérifie que les refus reste vert sur un serveur où plus personne ne passe outre.
Avertissements
- ⚠⚠ Un banc peut mesurer un autre exemplaire de la chose sans le dire. Ici ce n'était ni une cible inexistante ni un motif trop large : le montage était juste, la cible était juste, et c'est l'objet mesuré qui n'était plus celui qu'on croyait. Le premier réflexe devant un rouge inexplicable — relire le code — ne pouvait rien donner : le code était juste, et il l'était depuis la veille.
- ⚠
OPRIV_SEE_SECRETn'est lu nulle part non plus (visibilité des salons+s/+pdans NAMES, LIST, et la clé+k), niCHGHOST,SEE_REALHOST,ADMIN,SVSCMDS: six privilèges sur vingt et un. Ils ne sont pas corrigés ici — chacun est une décision distincte. - ⚠⚠ Rien de tout ceci n'est en service. Ces correctifs vivent dans le serveur C : ils n'atteindront la production qu'à la recompilation, celle qu'attend déjà le constat du point 55. Ils partiront donc dans le même geste, ce qui se décide avant, pas après.
4.29 1 version · 28 août 2026 1 création 1 évolution 2 bugs corrigés 2 constats 1 erreur 7 avertissements
4.29.0 — 28 août 2026
Le serveur DIT désormais qui peut poser quel mode — et deux des règles qu'il appliquait n'étaient pas celles qu'on croyait.
Demandé par SerVuS : que le panneau de gestion d'OverScript débloque ses cases selon le grade. Mesurer le serveur avant d'écrire a montré que deux des règles décrites n'existaient pas, et la décision prise a été de corriger le serveur, pas de griser côté client — un grisage seul se contourne avec n'importe quel autre client IRC : la règle n'existerait que pour qui utilise le nôtre.
Évolutions
CHANMODEREQ, un jeton005neuf : le serveur annonce le grade minimal de chaque mode de salon, sous la formeq:qa,o:klLjJntispRMCNSTzQVKcDuoh,h:beImv,*:O,!:rPdA—qaohvpour les préfixes,*pour l'opérateur IRC,!pour « le serveur seul ». ⚠ Dérivé de la tablechanmodes[], commechanmode_all_letters()dérive le004: aucune chaîne écrite à la main. Un mode ajouté demain y entre seul. ⚠ Jeton non standard : les autres clients l'ignorent, et son absence doit rester sans effet — c'est ce qui garde OverScript utilisable ailleurs.
Bugs corrigés
- ⚠⚠
+i— fermer le salon — n'exigeait qu'un HALFOP. N'importe quel%pouvait donc fermer la porte du salon de quelqu'un d'autre. DésormaisCHFL_CHANOPS. - ⚠⚠
+O— réserver le salon aux opérateurs IRC — n'exigeait qu'un CHANOP. Un@ordinaire pouvait fermer un salon à tout le monde sauf aux IRCops, y compris à lui-même dès sa prochaine entrée. Il faut maintenant être opérateur IRC. ⚠ Cela a demandé un mécanisme neuf :needsn'encode que des statuts de salon, et la valeur0y signifie déjà « serveurs et services uniquement ». D'où un champ à part,oper_only, testé avanthave & needs— l'ordre porte tout le sens, l'inverse aurait laissé un chanop passer par la branche du statut.
Erreurs
- ⚠⚠ « Seul l'Owner pose
+i» a été écrit, essayé, et ANNULÉ le jour même. Sur ce serveur, le fondateur d'un salon reçoitCHFL_CHANOPet rien de plus :+qest réservé aux services, donc aux seuls salons enregistrés. Exiger l'Owner rendait+iimpossible sur tout salon ordinaire — plus personne n'aurait pu fermer sa porte, sauf un IRCop passant outre. C'est la campagnetest-commandesqui l'a dit, par cinq constats rouges décrivant un usage parfaitement légitime : ouvrir un salon privé et le fermer. Tranché par SerVuS : le chanop. 📌 Un mode qu'on ne peut pas poser n'est pas une règle sévère, c'est un mode mort.
Créations
Servers/overtchat-hybrid/tests/test-modes-grade.py— 18ᵉ campagne, inscrite au lanceur le jour même. ⚠⚠ Elle ne porte aucune liste de modes : elle lit le jeton sur une vraie connexion, puis, pour chaque mode qu'il annonce, envoie deuxMODE— au grade juste en dessous (le serveur doit refuser) et au grade annoncé (il doit accepter). C'est le seul montage où le jeton ne peut pas mentir : trop peu, la première moitié rougit ; trop, la seconde.
Éprouvé
- 103 constats au vert, et cinq mutations attrapées, chacune sur les constats qui la concernent :
+iremis à halfop (2 rouges),+Oprivé de sa garde (3), le barème dérivé à l'envers (31), le jeton retiré du005(1, et la campagne s'arrête d'elle-même), une lettre trouée dans le jeton. - ⚠ Le
005a été LU sur une vraie connexion, jamais déduit du source. Le jeton fait 63 octets — le tampon des jetons deisupport_build()en tenait 64, et unsnprintfqui déborde tronque en silence : le client aurait lu un barème amputé de ses derniers modes, ce qui est pire que pas de barème. Porté à 96.
Avertissements
- ⚠⚠ Trois défauts de la campagne, tous du même genre : un banc qui ne mesure pas ce qu'il croit. Ils valent d'être nommés parce qu'ils ressemblaient tous les trois à un défaut du serveur.
- Les acteurs muets mouraient en cours de route. La campagne éprouve une trentaine de modes deux fois ; ceux qui ne sont pas à la manœuvre se taisent plus longtemps que
pingfreqet le serveur les coupe. Le mode alors sous examen paraissait coupable d'un dégât qui ne venait que de la durée. - Le succès était mesuré sur l'ÉCHO, pas sur l'ÉTAT.
+Orougissait alors que le serveur l'avait posé : l'avis d'outrepassement était arrivé le premier, et la fenêtre de lecture s'était refermée dessus. On lit désormais le324— etNAMESpour les grades, qui n'y figurent jamais. - ⚠⚠ Un serveur d'essai d'une mutation précédente avait survécu et tenait le port. Les deux campagnes suivantes ont mesuré son binaire en annonçant 31 constats rouges qui ne disaient rien de leur propre blessure — et ces chiffres ont failli être écrits ici comme une preuve. La campagne refuse maintenant de démarrer si le port est pris : parler au mauvais serveur ne se distingue en rien de marcher.
- ⚠⚠ ET LE QUATRIÈME A ÉTÉ DIAGNOSTIQUÉ DEUX FOIS À CÔTÉ. La campagne passait lancée seule et rougissait en dix-huitième position, sur les cinq modes de grade. J'ai accusé un délai de lecture, puis un tampon mal vidé ; le vert obtenu entre-temps n'était que de la chance. La cause est le « : » du paramètre final : le
353s'écrit… #salon :~Chef @Chanop, le premier nom porte le deux-points, et l'extraction le ratait — donc uniquement quand le pseudo cherché se trouvait en tête, au hasard de l'ordre des présents. 📌 Une intermittence n'est presque jamais une question de vitesse : c'est un cas particulier qu'on ne rencontre qu'une fois sur deux. Chercher d'abord ce qui VARIE d'une exécution à l'autre. ⚠ Le marqueur PING/PONG posé au passage reste un gain — la campagne ne dépend plus d'aucun délai — mais il n'était pas le correctif. - ⚠ Trouvé au passage, non corrigé :
OPRIV_OVERRIDEest défini (include/conf.h) et le mot-cléoverrideest accepté dans les blocs oper (src/conf.c), mais la constante n'est lue nulle part. Tout IRCop passe outre, que le privilège lui ait été accordé ou non, et le retirer d'un bloc oper n'a aucun effet. C'est un droit qui paraît réglable et ne l'est pas. - ⚠ Et :
INVEXest annoncé deux fois dans le005(src/ircd.c).
4.28 2 versions · 28 août 2026 2 créations 4 bugs corrigés 6 constats 1 erreur 1 avertissement
4.28.1 — 28 août 2026
Le serveur annonçait ses modes de salon différemment selon à qui il parlait — et cette fois la correction a été RECOMPILÉE et MISE EN SERVICE.
Bugs corrigés
- ⚠⚠ Le
PROTOCTLenvoyé aux serveurs pairs était périmé. Il annonçaitbeI,k,lLj,cimnprstuCDKMNOPQRSTVz: il y manquaitJ(groupe C, anti-retour après expulsion) etA(groupe D, salon archivé), tous deux présents dans la table et dans le005envoyé aux clients.PROTOCOL.mddocumentait la version fausse. ⚠ Le mode d'échec attendait son heure : un pair qui reçoit une liste incomplète ne sait pas quelle arité donner àJ, et décale ses paramètres dès qu'unMODEen porte un.
Créations
Web/tests/test-chanmodes.php— il dérive les quatre groupes de la tablechanmodes[]et compare les trois copies (le005, lePROTOCTL, la documentation), plus les grades, eux aussi écrits deux fois. ⚠ Il compare des ensembles de lettres, jamais des chaînes : l'ordre n'a aucune valeur de protocole, et un banc qui rougit sur un tri finit par ne plus être lu.
Éprouvé — la mise en service, sur accord de SerVuS
- Recompilation propre (
make cleanpuismake) : 34 fichiers, zéro avertissement. Les 17 campagnes du serveur passent — elles montent leurs propres serveurs surbuild/, jamais sur celui qui tourne. - Sauvegarde du binaire en service AVANT le geste, avec la commande de retour arrière écrite noir sur blanc avant qu'on en ait besoin.
- Constaté à 100 s — le contrôle tardif n'est pas du zèle, ce projet a vu un service mourir à 89 s :
overtchatdetirixactifs,NRestarts=0sur les deux (donc aucune mort suivie d'une relance), empreinte du binaire en service identique à celle qui vient d'être compilée, aucune erreur au journal. - ⚠⚠ Et le
005a été LU sur une vraie connexion, pas déduit du source :CHANMODES=beI,k,lLjJ,cimnprstuACDKMNOPQRSTVz. IriX a retrouvé son lien ULine et traitait déjà le trafic. Les 4 ports déclarés écoutent, les 2 robots tournent, 120 bancs au vert.
Erreurs
- ⚠ Fausse alerte de ma part en cours de vérification : j'ai cru voir 2 ports à l'écoute pour 4 déclarés. Mon filtre ne connaissait pas
6900(liens) ni8000(webchat), tous deux volontairement sur la boucle locale. C'est le travers déjà nommé — mesurer par un autre chemin que celui du code.
4.28.0 — 28 août 2026
Le panel de l'installateur avait une couche de rendu complète et pas un seul banc. Le premier écrit a trouvé un cadre qui ne se refermait pas.
Créations
Bootstrap/tests/test-ui-cadre.sh— le premier banc du rendu du panel : la mesure de ce qui se voit, la fermeture du cadre, et la cohérence des menus.
Bugs corrigés
- ⚠⚠ Une ligne plus longue que le cadre poussait la bordure droite dehors.
_ui_padborne son rembourrage à zéro, donc rien ne repliait le texte : le cadre se déchirait sur cette ligne et se refermait sur la suivante. Ce n'est pas un cas d'école — la largeur a pour plancher 64 colonnes, et le sous-titre du bandeau porteversion · user@host · chemin d'installation. Les lignes trop longues sont désormais coupées, et le disent par une ellipse. ⚠ La coupe retire les couleurs de la ligne coupée : couper au milieu d'une séquence d'échappement laisserait une couleur qui ne se referme jamais et déteint sur tout ce qui suit. - ⚠⚠ Le sous-menu « Services optionnels » cumulait quatre écarts — le seul des quinze menus du fichier à en porter.
[0] Retourétait offert sans aucune branche (il retombait dans la boucle parente, ce qui ressemble à un retour mais n'en est pas un) ;_menu_addétait appelé sans_menu_has, donc les clés étaient construites puis jamais consultées ; aucune branche par défaut, si bien qu'un choix invalide ne produisait rien du tout ; et les entrées s'affichaient hors cadre, les barres verticales pendant dans le vide. - Le choix du mode d'IriX n'avait aucune sortie annoncée. Pour en ressortir sans changer de mode, il fallait taper une réponse invalide ou faire Ctrl-D — sur un menu qui décide quels modules le bot chargera au prochain démarrage.
Éprouvé
- Passe de mutation à huit blessures, aucun trou, avec le nom du constat qui rougit relevé à chaque fois.
- ⚠ Deux trous trouvés par la mutation, et corrigés : les constats de cohérence des menus raisonnent par fonction, et ne voyaient donc pas le sous-menu imbriqué dans
_panel_install— lui retirer sa garde laissait le banc vert, la fonction en contenant une autre. Le cas est désormais nommé, faute de pouvoir se dériver : découper descaseimbriqués en bash demanderait un analyseur, et un analyseur approximatif rendrait des faux positifs — donc un banc qu'on finit par ignorer.
Avertissements
- ⚠ Deux dialectes coexistent dans ce panel : sur quinze menus, six seulement emploient
_menu_add/_menu_gate. Les neuf autres dessinent leurs entrées à la main, et trois le font encore en texte brut hors cadre. Le banc ne garde que les six — c'est écrit dans son en-tête plutôt que découvert.
4.27 1 version · 28 août 2026 3 créations 3 bugs corrigés 2 constats 1 erreur 1 avertissement
4.27.0 — 28 août 2026
Les deux derniers morceaux de la phase 5 devaient être du travail de documentation. Chacun cachait un défaut qui se voyait à l'écran.
Créations
Web/inc/niveaux.php— la correspondance des cinq échelles de gravité du projet, à un seul endroit.Web/tests/test-niveaux.php— il dérive les cinq listes de leurs sources et exige que la table les connaisse toutes.Bootstrap/tests/test-alerte-silence.sh— le silence des alertes, par cause.
Bugs corrigés
- ⚠⚠ La ligne la plus grave du journal s'affichait EN VERT.
::soc::alerte::leverécrit sa trace par untoupper, ce qui fabriqueCRIT— un mot qui n'appartient à aucune des cinq échelles. La coloration de la page des journaux ne connaissait queERRORetWARN; la ligne tombait donc sur la règle suivante, « contientSOC→ vert », posée par la ligne même qui l'avait écrite. Une affirmation qui ment dans le sens rassurant. - ⚠⚠ Le plafond des six heures était GLOBAL, et son commentaire mentait sur son propre code. Il annonçait « 6 h entre deux alertes identiques », mais l'étape en échec n'entrait nulle part dans la décision. Une mise à jour qui échoue à 03:00 sur un dépôt injoignable faisait taire six heures l'alerte de 04:00 disant que la base a disparu. Il compte désormais par clé, comme le bot le fait depuis le 16/08 — la même doctrine, portée au voisin qui ne l'avait jamais reçue.
- Le fichier d'état du silence se purge en s'écrivant : il grossissait d'une ligne par cause, pour toujours, sur une machine que personne ne regarde.
Erreurs
EDITIONS.mdcomptait cinq niveaux au serveur C ; l'enumen porte six —LOG_FATAL, celui dedie_fatal(), qui ne rend jamais la main. Et la ligne nommait les symboles :level_names[]imprimeWARN, jamaisWARNING.
Éprouvé
- Deux passes de mutation, dix-huit blessures, aucun trou, et le nom du constat qui rougit relevé à chaque fois.
- ⚠ Une leçon en chemin : deux gardes protègent la lecture d'un niveau. Cassées séparément, le banc reste vert ou rougit ailleurs ; cassées ensemble, une heure d'Eggdrop passe pour un niveau. Une mutation restée verte ne prouve pas que le code est inutile — elle peut aussi dire qu'une seconde garde tient.
Avertissements
- ⚠ La phase 5 n'est pas CONSTATÉE close. Son critère est qu'un incident provoqué volontairement parte avec le bon niveau — et cela envoie un vrai courriel. Le code est complet ; le geste revient à l'exploitant.
4.26 1 version · 28 août 2026 2 créations 3 modifications 2 bugs corrigés 3 constats 1 erreur 1 avertissement
4.26.0 — 28 août 2026
Le filet des rapports était posé, mais deux chemins d'envoi passaient à côté — et le seul moyen d'y remédier a été de l'écrire trois fois.
Créations
Web/tests/corpus-rapport.txt— un seul fichier de cas, lu par trois bancs dans trois langages. C'est la seule chose qui empêche les trois implémentations de la doctrine du rapport de diverger.Bootstrap/tests/test-doctrine-rapport-sh.sh— le banc de la moitié bash.
Modifications
Bootstrap/overtchat-setup.shporte la doctrine en bash. L'alerte de mise à jour filtre désormais les incidents, l'étape en échec et les chemins, et dit au lecteur combien d'éléments ont été retirés. ⚠ Les en-têtesTo:etFrom:n'y passent pas, délibérément : un message sans destinataire ne protège rien.Components/Service/lib/Tcl/Stable/IriX-std.tclporte la doctrine en Tcl.irix:send_mailfiltre le corps et le sujet — ce dernier finit sur une ligne de commandemutt -s, donc dans la table des processus de la machine.IriX/tests/test-std.tclreçoit le chapitre du corpus et celui du contournement.
Bugs corrigés
- ⚠⚠
--PASS2était nommé dans le filtre PHP et pourtant inatteignable.\bexige un caractère de mot avant le premier tiret, et devant--PASS2il n'y a jamais qu'une espace. Mesuré sur la forme réelle du fichier de robots —- b OverOp-T --PASS2 deuxmots— le filtre retirait zéro élément. C'est le secret de lien botnet, en clair dansIriX.user. Un motif qui cite un cas sans l'attraper ment mieux qu'un motif qui l'ignore. - La première écriture bash de l'étape IPv6 caviardait tous les horodatages : elle comptait deux deux-points au lieu de trois, et
14:00:00en a deux. Attrapée par le corpus le jour même.
Avertissements
- ⚠⚠ La doctrine est désormais écrite TROIS fois — PHP, Tcl, bash. Ni bash ni Tcl ne peuvent appeler le PHP : l'alerte de l'installateur part précisément quand l'installation va mal, et
IriX-std.tcltourne dans un Eggdrop. Un calcul écrit des deux côtés diverge un jour. La seule parade est le corpus partagé. Toute correction à l'une des trois commence par une ligne ajoutée au corpus — un cas absent n'y est éprouvé nulle part.
Erreurs
- ⚠⚠ Un banc écrit ce jour-là prouvait que la fonction existe, jamais qu'elle sert. Le chapitre Tcl « personne ne contourne le filet » rejouait le filtre lui-même sur la matière, puis constatait que le filtre filtre. Le corps de
send_maildébranché, il restait vert. Seule la passe de mutation l'a vu. Il intercepte désormais ce quemuttreçoit réellement.
Éprouvé
- Les trois implémentations rendent le même texte sur les 30 cas du corpus, chacune dans son banc.
- Passe de mutation à sept blessures, toutes attrapées : le
\bfautif remis en PHP, la frontière heure/adresse cassée en Tcl puis en bash, le filtre débranché du corps du courriel, celui des incidents de l'installateur, un cas gardien retiré du corpus, et le corpus réduit à ses commentaires. - ⚠ Par quel mécanisme cela voyage — mesuré, parce que c'est la question qui a manqué cinq fois :
overtchat-setup.shest la première entrée de son propreCORE_WATCHED, donc il se met à jour lui-même ;IriX-std.tclest écrasé par_irix_sync_source, sans garde « seulement si absent ».
4.25 1 version · 27 août 2026 2 créations 3 évolutions 3 bugs corrigés 4 constats 1 erreur 3 avertissements
4.25.0 — 27 août 2026
Un rapport automatique sortait de la machine sans que personne regarde ce qu'il emportait — et le pire chemin était le plus discret.
Créations
Web/inc/doctrine-rapport.php— le filtre des rapports. (Point 9, phase 5 deMarkdown/EDITIONS.md.)Web/tests/test-doctrine-rapport.php(47 constats) etIriX/tests/test-rapport-sql.tcl(22 constats).
Bugs corrigés
- ⚠⚠ Les gardes d'erreur SQL faisaient sortir la requête par courriel.
db_execetdb_selmettaient jusqu'à 201 caractères de la requête dans le détail d'une alerte — et toute alertecritpart par courriel. Une écriture ratée sur la table des comptes faisait donc sortir pseudos, adresses et empreintes de mots de passe, sans que personne ait rien écrit ni rien décidé. - ⚠⚠ Et la clé fuyait aussi, derrière un commentaire juste. Le code disait « la clé porte le début du message MySQL, jamais la requête » — vrai à la lettre, faux en pratique : sur une erreur de syntaxe, MySQL recopie lui-même un fragment de la requête après
near '. La clé partait en clair quatre lignes au-dessus du détail. ::soc::sql_formene rend désormais que le verbe et la table ;::soc::sql_err_netcoupe ce que MySQL recopie, et garde tout ce qui précède — « Unknown column », « Table doesn't exist » nomment ce qu'il faut réparer.
Évolutions
- Le filtre est appliqué à
Tools/alerte-courriel.php, point de passage unique : le Tcl ne poste rien lui-même, il lanceexec phpdessus. Filtrer là couvre les dix appels à matière brute d'un seul geste. - ⚠ La clé, la source et le sujet passent par le filet, pas seulement le détail — sept des dix appels construisent la clé avec un fragment de message système.
- Le courriel dit combien d'éléments ont été retirés, et dit aussi que le filet a des trous : un lecteur qui le croirait entièrement expurgé cesserait de faire attention en amont.
Erreurs
- ❌
Markdown/EDITIONS.mdfaisait de cette phase le prolongement d'une fonctionsans_secret()qui n'a jamais existé. Balayage refait : aucun masquage d'aucune sorte dans tout le dépôt. La parade était à écrire, pas à reprendre.
Avertissements
- ⚠⚠ LE FILET NE RATTRAPE PAS LE CAS QUI L'A MOTIVÉ, et c'est mesuré. Une requête SQL ne contient ni IP, ni courriel, ni secret au format
mot=valeur: le filtre n'en retire rien, et le pseudo sort. La correction est donc à la source, et le banc affirme cette limite au lieu de la taire. Élargir un filet jusqu'à ce qu'il attrape le cas du jour est la façon la plus sûre de le rendre inutile pour tous les autres. - ⚠⚠ Le Tcl n'est pas rechargé : les deux procs n'existeront dans le processus vivant qu'après un redémarrage d'IriX. Ce geste n'a pas été fait.
- ⚠ Deux chemins d'envoi restent hors du filet, et le banc les nomme :
Bootstrap/overtchat-setup.sh(sendmail -t) etComponents/Service/lib/Tcl/Stable/IriX-std.tcl(mutt) — ce dernier étant dans la bibliothèque distribuée.
Éprouvé
- Deux pièges de motif, tous deux mesurés sur les journaux vivants. Un motif IPv6 naïf caviarde 100 % des lignes — 1304 sur 1304, puis 447 sur 453 — parce qu'il prend l'horodatage
14:00:00pour une adresse. Et un motif alternatif mal posé découpait une adresse abrégée par le milieu :fe80::1ff:fe23:4567:890adevenaitfe80[retiré:ip]:fe23:4567:890a. - Le banc Tcl exécute les deux procs au lieu de lire leur source : écrire du code prouve qu'il existe, jamais qu'on peut s'en servir.
- Mutation à 16 blessures, sans trou — et elle a trouvé quatre constats faux que la relecture n'avait pas vus : un qui vérifiait qu'une variable existe au lieu qu'elle serve ; un qui lisait le nom d'un fichier dans un commentaire au lieu du
require; un qui accusait unputlogdélibéré ; et un qui restait vert quand le motif du masque cassait, parce que le motif du courriel effaçait l'hôte — en laissant le pseudo. - Campagne complète : 115 bancs au vert, 5315 constats.
4.24 2 versions · 27 août 2026 2 créations 7 évolutions 5 bugs corrigés 9 constats 2 erreurs 5 avertissements
4.24.1 — 27 août 2026
Une correction ne pouvait pas atteindre une installation — et le banc qui aurait dû le dire regardait le dépôt.
Bugs corrigés
- ⚠⚠ Le défaut est dans le mécanisme de livraison, pas dans ce qui est livré.
Servers/overtchat-hybrid/contrib/install-service.shne posait les quatre fichiers de politique que s'ils manquaient. Une installation déjà en place gardait donc, pour toujours, la documentation du jour de sa pose : aucune correction ne pouvait plus l'atteindre, ni sur la production, ni chez un tiers. - Mesuré, pas supposé : le format d'une empreinte a été corrigé dans le dépôt le 16/08 — quatre champs au lieu de trois. Le 27/08, onze jours plus tard, le fichier posé en production annonçait toujours trois champs. ⚠ Et le format à trois est accepté en lecture : il vaut une itération au lieu de cent mille, sans le moindre avertissement. Un exploitant qui suit son propre fichier obtient une protection cent mille fois moins chère à casser, qui a l'air de fonctionner.
- ⚠⚠ Le registre affirmait « corrigé dans les 8 endroits, dont le fichier installé ». La première moitié était vraie, la seconde fausse.
Évolutions
- Le modèle (
.example) est désormais redéposé à chaque passage, hors de toute condition : il n'est lu par aucun serveur et n'est rempli par personne, donc le remplacer ne peut rien détruire. La politique (.conf) n'est toujours écrite que si elle manque — les entrées de l'exploitant sont intouchables. - Et une documentation vieillie est dite : l'exploitant est renvoyé au modèle frais posé à côté. ⚠ La comparaison porte sur les seules lignes de commentaire — comparer les fichiers entiers crierait dès la première entrée ajoutée, pour toujours, et un avertissement permanent n'est plus un avertissement.
- ⚠ La forme n'est pas neuve : ce script appliquait déjà exactement cette doctrine à
overtchat.conf, vingt lignes plus haut. Elle n'avait jamais été étendue aux politiques.
Erreurs
- ⚠⚠ Un commentaire mentait sur ce que son propre code faisait. Le docbloc de
botpool_retirer()annonçait « les QUATRE endroits en base » au-dessus d'une énumération qui en portait cinq, et appelait la dernière « le septième endroit » d'un total qui en compte huit. Le même faux était recopié dansWeb/inc/botpool.phpet dans le registre. Les chiffres sont retirés — on énumère, on ne dénombre plus. - ⚠ La liste des « cinq pseudonymes à protéger » du point 10 était périmée, et fausse dans les deux sens : trois noms disparus y figuraient, et un robot bien vivant (
OverOp-A) n'y figurait pas.
Éprouvé
- ⚠⚠ Le banc est resté vert onze jours pendant que le défaut nuisait.
test-conf-modele.shportait déjà le bon constat — mais il balaie le dépôt, qui était juste. Un banc qui ne lit jamais l'installation ne peut pas voir un défaut de livraison. Il passe de 9 à 15 constats, dont un qui regarde l'installation : le seul du fichier à le faire. - ⚠⚠ Et ce constat a rougi POUR DE VRAI avant que le geste soit fait — l'épreuve qu'on ne peut pas fabriquer. Mutation à 5 blessures, sans trou, dont le retour de l'ancienne condition liée.
test-botpool-retrait.phpgagne un chapitre de complétude : autant d'étapes décrites au docbloc que d'écritures réellement faites. Il passe à 45 constats, mutation à 5 blessures, sans trou. ⚠ Il a fallu le corriger en naissant : il accusait le texte qui répare — celui qui cite l'ancien chiffre pour le dénoncer — au lieu de celui qui affirme. On cherche désormais la forme affirmative, pas le mot.- Les modèles à jour sont posés en production — sauvegarde dans
/home/servus/sauvegarde-modeles-20260827-170103. ⚠ Trois étaient périmés, pas un. Les.confn'ont pas été touchés : ils portent 84 et 12 entrées vivantes. - Campagne complète : 113 bancs au vert, 5246 constats.
Avertissements
- ⚠
Servers/n'est distribué par aucun canal — décision encore ouverte. La correction profite donc à la production et à qui clone le dépôt, pas encore à un tiers qui passe par l'installateur.
4.24.0 — 27 août 2026
L'installateur sait installer sans personne devant — et il refuse de partir plutôt que d'inventer une réponse.
Créations
--auto, dansBootstrap/overtchat-setup.sh: les six étapes de l'assistant enchaînées sans une seule question — site → serveur IRC → Eggdrop → IriX → unités systemd. Le mode manuel n'a pas bougé : c'est l'assistant, et il existait déjà. (Point 48 du registre, deuxième morceau.)Bootstrap/tests/test-auto.sh— 42 constats.
Évolutions
- ⚠⚠ Le mode automatique ne lit JAMAIS l'entrée standard, et c'est tout le sujet. Un
readsans personne devant ne rend pas une réponse : il rend une chaîne vide obtenue par EOF, que plus rien ensuite ne distingue d'une réponse donnée. L'installation se serait poursuivie sous des valeurs que personne n'a choisies, en affichant « configuré »._site_askrend donc la valeur prévue et le dit surstderr— jamais surstdout, qui est la réponse puisque la fonction est appelée dans un$( ). - Un contrôle avant vol,
_auto_prevol, placé avant la première écriture sur le disque : il exige le nom de l'installation,OVERTCHAT_SQL_PASSet une élévation sans mot de passe, puis nomme ce qui manque. Un refus ne laisse donc rien derrière lui — mesuré : le bac à sable reste vide et le journal de la production intact. - Les valeurs se donnent par l'environnement :
OVERTCHAT_DOMAINE,OVERTCHAT_RESEAU,OVERTCHAT_COURT,OVERTCHAT_MAIL_FROM,OVERTCHAT_MAIL_TO,OVERTCHAT_SERVEUR,OVERTCHAT_AUTO. Ce qui reste au défaut d'exemple est annoncé, pas subi. _as_rootpasse ensudo -nsous--auto: une invite de mot de passe que personne ne remplira n'attend plus sans fin et sans message.
Bugs corrigés
(Trouvés en écrivant le mode, pas après.)
- ⚠⚠ Le gel. Les trois boucles de
_site_installreposent leur question tant que la réponse ne convient pas. En automatique, la réponse suivante est identique à la précédente : le refus se serait répété sans fin et sans rien écrire à l'écran. Pas un échec — un arrêt muet._auto_bouclecoupe au second tour : un second tour n'est pas une seconde chance, c'est la preuve que la valeur fournie est refusée. - ⚠⚠ Le secret vide. Le mot de passe SQL d'IriX n'a de valeur par défaut nulle part. Sans garde,
IriX.jsonaurait été écrit avecpass:"", l'écran aurait dit « configuré », et le bot n'aurait jamais joint la base.
Avertissements
- ⚠ Une seule question de consentement reçoit « non » : installer une archive dont l'empreinte n'a pas pu être vérifiée. Les autres portent sur ce que l'installation vient faire —
--autoest cet accord. Celle-là demande de passer outre un contrôle d'intégrité, sur une archive qui s'extrait par-dessus la racine d'installation. - ⚠ Anope n'est pas dans le socle automatique : son mot de passe de lien se lit dans le bloc
linkdu serveur IRC, qui n'existe pas encore sur une installation neuve. Il se pose ensuite, paranope-config. - ⚠ Le serveur posé par défaut est
unrealircd, pas celui du projet.overtchat_ircdn'est distribué par aucun canal — décision encore ouverte —, donc un mode automatique qui le choisirait échouerait chez tout tiers, au milieu de l'installation. - ⚠⚠ Une installation complète de bout en bout n'a jamais été menée. Le banc éprouve les gardes, pas le parcours : il y faudrait une machine nue et la compilation d'un serveur IRC. C'est le geste qui manque, et il va sur la machine tierce. Écrire du code prouve qu'il existe, jamais qu'on peut s'en servir.
Éprouvé
Bootstrap/tests/test-auto.sh: 42 constats, et une passe de mutation à 16 blessures, sans un seul trou — le gel, le secret vide, l'alerte écrite sur la mauvaise sortie, l'ordre du contrôle avant vol, le menu non sauté, le troisième paramètre ignoré.- ⚠ Le constat qui vaut pour la suite est celui de complétude : il compare le nombre de boucles de question de
_site_installau nombre de gardes. Une boucle ajoutée demain sans sa garde fera rougir le banc. - Lancé pour de vrai dans un
HOMEdétourné : refus en nommant les trois manques, sortie en code 2, rien de créé, journal de production intact. - Campagne complète : 113 bancs au vert.
4.23 3 versions · 24 août 2026 1 création 22 évolutions 6 bugs corrigés 2 constats 7 avertissements
4.23.2 — 24 août 2026
Après chaque salon approuvé, le panel demandait à l'exploitant d'éditer un fichier que plus rien ne lit.
Bugs corrigés
- ⚠⚠ Une consigne morte, affichée à chaque approbation de salon. Le panel disait : « ajouter
allow channel { … }àConfig/unrealircd/salons.confpuis rehash […] Sans le premier, le salon reste inaccessible. » Les trois moitiés de la phrase étaient fausses en même temps — ce fichier n'est lu par aucun code depuis le retrait d'UnrealIRCd le 28/07, sa syntaxe est celle d'un serveur qui ne tourne plus, et le « rehash » visait un démon arrêté. Un exploitant qui suivait la consigne éditait un fichier inerte, croyait le salon ouvert, et le demandeur se prenait un refus en essayant d'entrer. - ⚠ Le second endroit, plus discret : l'encart de la page des demandes nommait le même fichier et disait qu'il fallait « ajouter l'exception à la main ». La fermeture, elle, était bien réelle — mais le fichier était le mauvais, et « à la main » niait l'existence de l'outil.
- Les deux nomment désormais le geste vivant :
Tools/salons-exceptions.sh --appliquer, qui relit la table des salons, pose les exceptions et recharge le service.
Évolutions
- Un banc neuf,
Web/tests/test-consignes.php, garde une FAMILLE et non un cas. Le défaut n'a rien de propre aux salons : il se reproduira le jour où un outil sera renommé. Le contrôle dérive donc la liste des scripts cités depuis les sources du panel elles-mêmes, et exige que chacun existe. Il vérifie aussi que le script prescrit comprend l'option qu'on dit de taper, et que celle-ci est lue et pas seulement documentée.
Avertissements
- ⚠⚠ Ce n'était pas un oubli de documentation : la documentation, elle, avait été corrigée.
SECURITY.mddéclare ce fichier inerte en capitales,ARCHITECTURE.mdetRESEAU.mdle qualifient d'hérité, et le fichier s'auto-dénonce dans son propre en-tête. Seul le texte affiché n'avait pas suivi — et c'est le pire des deux mondes : le document que personne ne lit dit vrai, l'écran que tout le monde lit ment. - ⚠ Le remplaçant existait depuis des semaines.
Tools/salons-exceptions.sha été écrit pour combler exactement ce trou — il le dit dans son propre en-tête. Personne n'avait mis le message à jour. - ⚠ Aucun banc ne couvrait ces pages, et aucun n'aurait rougi si la consigne avait été corrigée. C'est ce trou-là que le banc neuf ferme.
Éprouvé
Mutation, quatre passes, quatre rouges : represcrire l'héritage inerte (3 échecs), ne plus nommer le geste (1), rendre le script introuvable (4), lui retirer l'option qu'on dit de taper (1). Restauré, 12 constats verts.
⚠ Un cinquième constat existe pour empêcher la sur-correction : le « rehash » du panel vise l'Eggdrop, et celui-là est bien vivant. Une garde trop large, écrite dans l'élan, l'aurait interdit avec le reste.
4.23.1 — 24 août 2026
Le bilan de la page Versions écrivait « 1 créations ».
Bugs corrigés
- ⚠ Les pastilles du bilan par branche ne s'accordaient pas au nombre. Les libellés étaient figés au pluriel, si bien qu'une rubrique ne portant qu'une seule entrée affichait « 1 créations », « 1 évolutions », « 1 bugs corrigés ». Le défaut se voyait surtout à l'ouverture d'une branche — c'est-à-dire au moment où la page est la plus regardée : le jalon 4.23.0, publié une demi-heure plus tôt, l'affichait.
- ⚠ Le compteur de versions critiques accordait déjà, seul de la page. Une garde qui couvre un compteur sur six rassure sans protéger.
- Le bilan général accorde désormais aussi « version », « branche » et « critique ».
Évolutions
- Chaque rubrique porte maintenant ses deux formes, écrites plutôt que dérivées. ⚠ L'accord ne s'obtient pas en ajoutant un « s » : « bug corrigé » en prend deux, et une règle mécanique se serait trompée sur la moitié des cas.
journal_accord()choisit entre les deux — et rend le singulier pour zéro, comme le veut le français.
Éprouvé
test-journal.php passe de 34 à 46 vérifications. Le constat qui compte n'est pas que la fonction soit juste, mais que la page l'appelle : le banc lit le source de Web/site/versions.php et exige qu'il n'y reste aucun libellé affiché sans accord — une fonction juste que rien ne branche est le défaut le plus fréquent de ce projet.
Mutation, quatre passes, quatre rouges : la page cesse d'appeler l'accord (2 échecs), l'accord rend toujours le pluriel (3), « bug corrigé » n'accorde que son premier mot (1), une rubrique perd sa forme singulière (3). Restauré, le banc revient au vert.
4.23.0 — 24 août 2026
Une branche entière passée à faire marcher le projet CHEZ QUELQU'UN D'AUTRE.
La 4.19 s'était ouverte le 16 août sur une amélioration du chat. Ce qui l'a occupée ensuite est d'un autre ordre, et c'est le fil qui relie presque tout ce qui suit : la plupart des défauts corrigés marchaient déjà ici et ne marchaient nulle part ailleurs. Un correctif appliqué en production ne suit pas l'installateur, l'archive du site ni la mise à jour ; il fallait chaque fois se demander où vit vraiment la chose qu'on venait de réparer.
Cette version regroupe la branche entière sous un jalon. Elle n'apporte aucun code nouveau par elle-même.
⚠ Les branches 4.20, 4.21 et 4.22 n'existent pas et n'ont jamais existé. Le numéro saute volontairement ; rien n'a été perdu entre les deux.
Évolutions
Ce qui change pour qui installe le projet ailleurs
- L'installateur pose enfin ce qu'il annonçait. Les unités systemd, les fichiers d'Apache et les quatre familles de fichiers système atteignent désormais une installation tierce — auparavant l'écran les affichait, et rien n'arrivait. Une installation neuve laissait quatorze services au repos sans que rien ne le dise.
- Eggdrop se pose et IriX démarre.
make installvisait son propre répertoire source ettcl-tlsmanquait : deux défauts qui arrêtaient l'installation net. - Le client SQL est nommé MariaDB explicitement. Le métapaquet employé jusque-là menait à MariaDB sur Debian et tirait MySQL 8 sur Ubuntu, où il entre en conflit avec le serveur que tout le reste du projet suppose.
- Le premier enrôlement du propriétaire fonctionne. Il n'avait jamais pu fonctionner, et le compte naissait avec un masque sans ident.
- Reposer les contrôles d'intégrité est devenu un geste que l'on décide, au lieu d'un effet de bord. Deux fichiers de configuration naissaient par ailleurs lisibles par tout le monde.
- La liste des communes part enfin avec l'archive. Le contrôle de ville était éteint en silence sur toute installation tierce — il fonctionnait ici, et nulle part ailleurs.
- L'assistant d'installation prend les couleurs du site.
Ce qui change pour le robot et le réseau
- Plus aucun cycle du bot ne peut s'arrêter en silence. Six boucles le pouvaient ; chaque tâche périodique déclare maintenant son rattrapage, et un contrôle refuse celles qui ne le déclarent pas.
- IriX ne fabrique plus des minuteurs à l'infini. Il s'en étouffait.
- L'alerte parle enfin de la panne qui l'empêchait de parler — le cas précis où elle se taisait.
- Après une rupture de lien, le SID choisi est celui que le serveur accepte.
- Les journaux d'IriX ont un plafond de taille, et une veille extérieure le surveille.
Ce qui change pour les visiteurs
- Le chat ne s'ouvre plus en double et ne redemande plus le pseudonyme. Sortir du chat n'ouvre plus un onglet à chaque clic.
- La touche Tab complète les pseudonymes et les salons.
- Le grade s'affiche devant le pseudo, et les grades du réseau ne se confondent plus avec ceux du salon. La liste des pseudonymes réservés se replie quand elle s'allonge.
- Le champ « Ville » peut se remplir tout seul, si le visiteur le demande — et seulement s'il l'accepte.
- La charte disait trois choses fausses à ses lecteurs, et un contrôle croise désormais les durées qu'elle annonce avec celles que le code applique. La page d'accueil, elle, annonçait un nombre de serveurs compatibles périmé : il se dérive maintenant de sa source au lieu d'être écrit à la main.
Ce qui change au panel
- Les traces d'administration reçoivent une date de péremption : elles gardaient des adresses sans aucune purge.
- Un bouton demande un robot de plus — la file d'installation des robots n'avait aucun producteur.
- Une fin de prêt n'écrit plus qu'une seule ligne au journal.
Créations
Vingt-huit fichiers, dont vingt-quatre bancs d'essai : six pour l'installateur (Bootstrap/tests/), six pour le robot (IriX/tests/), douze pour le site et le panel (Web/tests/). S'y ajoutent la veille extérieure d'IriX (Tools/veille-irix.php) et son unité de minuterie, plus une migration de base.
Rien n'a été supprimé du dépôt pendant cette branche.
Avertissements
- ⚠⚠ Le motif dominant de la branche n'est pas refermé pour autant. « Cela marche ici » ne dit rien de ce que reçoit un tiers, et les bancs d'essai ne l'attrapent pas : plusieurs de ces défauts sont restés verts pendant qu'ils cassaient une installation neuve. Le contrôle qui les a trouvés a toujours été le même — installer pour de vrai, ailleurs.
- ⚠⚠ Les bancs d'essai eux-mêmes ont menti, et souvent. Une dizaine d'entrées de cette branche le racontent : un banc resté vert parce qu'il mesurait le commentaire du correctif, un autre qui fabriquait lui-même la commande qu'il devait trouver, un troisième qui mesurait du HTML là où le défaut vivait dans le navigateur. Ce qui l'a dit à chaque fois, c'est la passe de mutation — casser exprès ce que le banc prétend garder —, jamais la relecture. Un banc qui n'a jamais été vu rougir ne garde rien.
- ⚠ La distribution du serveur reste entière.
Servers/n'est distribué par aucun canal ; chez un tiers, l'installation d'Overtchat-Hybrid échoue et le réseau tourne sur un autre serveur, donc aucun correctif du serveur ne l'atteint. C'est une décision en attente, consignée dansMarkdown/CHANTIERS.md. - ⚠ Ce que ce jalon ne dit pas : ce qui reste ouvert ne se lit pas ici mais dans
Markdown/CHANTIERS.md, affiché au-dessus de cette page, et il s'y dérive des titres plutôt que de se recopier.
4.19 57 versions · 16 août 2026 → 24 août 2026 6 critiques 43 créations 50 évolutions 28 modifications 71 bugs corrigés 1 suppression 10 constats 54 erreurs 87 avertissements
4.19.56 — 24 août 2026
Deux lignes du carnet avaient perdu leur chevron de citation, et la page publique le montrait : quatre astérisques en clair et un tableau disparu.
Bugs corrigés
- ⚠⚠ Un tableau entier ne naissait pas. Dans le point 43, la ligne d'en-tête était citée (
> | … |) mais son séparateur|---|---|ne l'était pas. Le moteur ne voyait plus deux lignes de tableau mais un paragraphe suivi d'un autre : l'en-tête retombait en texte et ses barres verticales s'affichaient au visiteur. Aucune balise n'était pourtant déséquilibrée — c'est exactement le mode d'échec quetest-markdown.phpavait été armé pour voir le 14/08.
- ⚠ Un gras traversait la frontière d'une citation. Toujours au point 43, le
**s'ouvrait sur une ligne non citée et se fermait sur la ligne citée suivante. Le moteur met en forme à l'intérieur d'un bloc, jamais entre deux : les quatre astérisques partaient en clair sur la page.
Éprouvé
Le contrôle rougissait avant, il est vert après, sur le même document et à cinq minutes d'intervalle : 2 astérisques doubles survivants → 0, et 127 <table> rendus pour 128 suites de lignes en « | » → 128 pour 128. Le compte des groupes se dérive du source, il n'est pas écrit.
Avertissements
- ⚠⚠ Le banc était ROUGE depuis 24 commits. Le gras a été introduit le 23/08 à 19:36 (
65ca1a3), le tableau le 24/08 à 17:32 (d07694f) ; entre les deux, la campagne n'a pas été relancée et le rouge n'a été entendu par personne. Le hookpre-commitne lance pas les bancs — il valide la syntaxe et le manifeste — donc rien n'attrape ce cas au moment où il naît.
- ⚠ Le motif est le même deux fois : une ligne à qui il manque son
>. Il ne casse ni la syntaxe ni un banc de balises ; il ne se voit qu'en rendant le document et en comparant au source. Les deux fautes sont nées dans des encadrés écrits vite, en fin de constat.
4.19.55 — 24 août 2026
Le client SQL est nommé MariaDB, et non plus un métapaquet qui change de sens selon la distribution.
Bugs corrigés
- ⚠⚠
default-mysql-clientmène à MariaDB sur Debian, mais tire MySQL 8 sur Ubuntu — qui entre en conflit avecmariadb-serveret rend « held broken packages ». Or la familledebiande l'installateur couvre les deux : Ubuntu y est rangé parID_LIKE. Un exploitant Ubuntu qui suivait l'installateur obtenait donc un client MySQL et ne pouvait plus poser le serveur que tout le reste du projet suppose — l'écran de configuration demande littéralement « Hôte MariaDB ».
- ⚠ Les quatre autres familles nommaient déjà MariaDB explicitement (
mariadb,mariadb-clients,mariadb-client).debianétait la seule à passer par un métapaquet ambigu : ceci l'aligne.
Éprouvé
Mesuré sur les deux distributions avant de trancher : le paquet est déjà installé en production (Debian 13, 1:11.8.6) et disponible sur Ubuntu 24.04 (1:10.11.14). Rien ne régresse.
test-deps-eggdrop.sh passe à 20 vérifications, dont cinq qui exigent que chaque famille nomme un paquet MariaDB. Mutation : remettre le métapaquet → 2 échecs.
4.19.54 — 24 août 2026
Les fichiers de configuration système atteignent enfin une installation tierce — et ils y arrivent ADAPTÉS, pas bruts.
Ajouté
Config/{systemd,polkit,sudoers.d,logrotate.d,apache}partent avec l'archive. Jusque-là ils n'atteignaient aucune installation tierce : ni par l'installateur, ni par le site, ni par l'archive, ni par un clone. L'écran « Services système » y affichait0/0sur les quatre familles, définitivement.
- ⚠⚠ Par l'archive, et non par une liste de fichiers. Le code qui pose ces fichiers dérive sa liste du disque, et trois commentaires l'imposent. Énumérer aurait réintroduit la liste centrale qu'ils interdisent — un fichier ajouté demain aurait été posé ici et absent ailleurs, en silence.
- ⚠⚠ Et ils sont ADAPTÉS avant d'être posés. Livrés bruts, le sudoers et le polkit auraient accordé des droits à « servus », qui n'existe pas chez un tiers : une installation qui a l'air configurée sans l'être, pire que l'état d'avant. La substitution vise des motifs nommés, jamais un remplacement global qui réécrirait les commentaires.
- ⚠ Apache est livré mais pas adapté, et une garde refuse de le poser sur une machine dont le compte diffère : son vhost porte un domaine et des chemins de certificats en dur, et Apache refuserait de démarrer.
Éprouvé
test-systeme.sh passe de 62 à 73 vérifications. Deux mutations, deux attrapées — dont le remplacement global, pris par le constat qui vérifie qu'un commentaire n'est pas réécrit.
⚠ Le constat sur une vraie installation tierce reste à prendre.
4.19.53 — 24 août 2026
L'installateur peut de nouveau poser Eggdrop, et IriX démarre : deux défauts qui empêchaient TOUTE installation neuve, l'un depuis longtemps.
Bugs corrigés
- ⚠⚠
make installd'Eggdrop visait son propre répertoire de sources. Le Makefile d'Eggdrop refuse ce cas en toutes lettres. La compilation réussissait — binaire et modules produits — puis l'installation mourait, et le rollback effaçait les traces derrière lui. Les sources sont désormais déplacées dans.src-build, etmake installvise le répertoire que la suite attend.
- ⚠⚠
tcl-tlsn'était déclaré nulle part, alors queIriX-sql.tclfaitpackage require tlsà sa dixième ligne. Une fois Eggdrop posé et IriX déployé, le démarrage mourait sur « can't find package tls ». Il est ajouté aux dépendances d'IriX et traduit dans les cinq familles de paquets.
- ⚠ Un nom de paquet faux ne peut plus mentir :
_pkg_presentteste la capacité — il demande à Tcl s'il sait chargertls— et une famille inconnue le déclare manquant, jamais présent. C'est la leçon déjà écrite pourmysqltcl: répondre « présent » sans savoir est le pire des choix.
Ce que ces défauts apprennent
Ni l'un ni l'autre n'était une affaire de distribution ou de droits : ils échouaient partout. La production ne pouvait pas les voir — elle n'a jamais repassé par ce chemin, et tcl-tls y avait été posé à la main un jour. Il a fallu monter une machine vierge pour qu'ils apparaissent.
⚠ Et les correctifs ont failli rester au brouillon : le commit du 23/08 qui porte le nom de ce point n'avait touché que le registre. C'est une mesure — grep -c rendant encore 0 — qui l'a rattrapé.
Éprouvé
Bootstrap/tests/test-deps-eggdrop.sh, banc neuf : 14 vérifications, trois mutations attrapées. Et ce banc s'est fait prendre en s'écrivant — son constat comptait le make install cité dans un commentaire en plus du vrai appel.
⚠ Le constat qui manque : l'installation neuve de bout en bout, sans correctif local.
4.19.52 — 24 août 2026
Tab complète les pseudonymes et les salons dans le champ de message, comme dans tout client IRC.
Ajouté
- La complétion au clavier. Tab complète le mot sous le curseur : les présents du salon affiché, ou les salons quand le mot commence par
#ou&. Les appuis successifs font défiler les candidats. Un:est ajouté en tête de ligne — l'usage IRC pour interpeller — et une simple espace ailleurs.
- ⚠
Shift+Tabreste le chemin de sortie clavier. Sans cette clause, le champ deviendrait un piège à focus : il reçoit le focus tout seul à l'ouverture, et plus rien n'en sortirait sans la souris. De même,preventDefault()n'est appelé que si une complétion a réellement eu lieu, sinon la touche paraîtrait morte sur un champ vide.
Ce que ce défaut apprend
Ce n'était pas une régression : la complétion n'avait jamais existé ici. Les cinq causes classiques d'un ordre de tabulation cassé ont toutes été écartées avant d'en arriver là — aucun tabindex positif, aucun gestionnaire interceptant Tab, aucun réordonnancement CSS, aucun élément focusable invisible, ordre DOM intact. Le focus sautait simplement au bouton « Envoyer », voisin immédiat dans le DOM.
Éprouvé
Web/tests/test-chat-completion.php, banc neuf : 41 constats au vert. Les deux fonctions éprouvées sont pures et sont extraites de chat.js puis lancées sous node — on éprouve l'original, jamais une copie réécrite en PHP.
Trois mutations, trois attrapées : garde du dièse retirée (1 échec), comparaison rendue sensible à la casse (6 échecs), et regex construite depuis le préfixe — celle-ci fait échouer node lui-même, ^[z n'étant pas une expression régulière valide. C'est la démonstration de ce que le projet répète : un pseudo IRC contient des caractères qu'une regex interprète.
⚠ Le constat qui manque, et il est nommé : le geste au navigateur.
4.19.51 — 23 août 2026
Deux liens internes portaient _blank écrit à la main : le correctif de la veille les avait manqués, et c'est la production qui l'a dit.
Bugs corrigés
- ⚠⚠
?p=charteET?p=reglementÉCHAPPAIENT AU CORRECTIF. La passe de sortie laisse intact tout ce qui porte déjà untarget— c'est voulu, mais cela signifie qu'un_blankécrit à la main lui échappe entièrement. Les deux liens sont passés au nom de l'onglet compagnon.
Comment on l'a su
Par un curl sur la production, après déploiement : la page du chat servait 44 liens sur l'onglet compagnon et 6 en _blank, dont ces deux-là. Le banc, lui, était au vert.
⚠⚠ Son constat de rendu ne les atteint pas : ces liens vivent dans un <details> que le rendu du banc ne produit pas. Un constat de source a donc été ajouté — aucun gabarit ne doit écrire target="_blank" sur un href interne. La mutation le confirme : remettre _blank en dur donne un échec, et c'est bien le constat de source qui l'attrape, jamais celui du rendu.
4.19.50 — 23 août 2026
Sortir du chat n'ouvre plus un onglet à chaque clic, et y revenir ne redemande plus le pseudo — c'était le même _blank qui causait les deux.
Bugs corrigés
- ⚠⚠ UN ONGLET DE PLUS À CHAQUE SORTIE. La passe qui dirige les liens de la page du chat posait
target="_blank"— un nom anonyme, qui ne désigne aucun contexte réutilisable. Les liens internes reçoivent désormais le nom d'un onglet compagnon, dérivé de l'identité du réseau comme l'est déjà celui du chat : deux onglets en tout, quel que soit le nombre de clics.
- ⚠⚠⚠ ET C'EST LE MÊME CODE QUI REDEMANDAIT LE PSEUDO. Un onglet ouvert avec
noopenervit dans un groupe de contextes isolé, et un nom de fenêtre ne se résout que dans son propre groupe. Depuis cet onglet, lewindow.open('', NOM)du script de retour ne retrouvait pas l'onglet du chat : il obtenait une fenêtre vierge, la refermait, et laissait le lien naviguer — la page chat se rechargeait à neuf, d'où le panneau pseudo alors qu'une conversation était ouverte à côté.
- ⚠ Les liens qui SORTENT du site gardent
_blank+noopener, et c'est délibéré : leur donner notre nom d'onglet les mettrait dans notre groupe de contextes, où la page distante pourrait lire et réécrirewindow.opener.location.
Ce que ce défaut apprend
C'était une régression de la 4.19.43, quand le chat est passé de « s'ouvre en onglet » à « s'ouvre sur place » : l'onglet du chat n'était plus créé par le lien nommé, donc le lien de parenté entre les deux contextes a disparu. Personne ne l'a rattrapé.
⚠⚠ Et le banc est resté vert parce qu'il mesure du HTML. test-chat-onglet.php exigeait _blank + noopener sur quatre constats, et admet lui-même ne pas savoir exécuter de JavaScript. Il codait l'ancienne règle et la protégeait. Ses constats sont réécrits, et trois nouveaux gardent la moitié qu'il ne fallait pas corriger : un lien externe reste anonyme, un lien en // aussi, et le nom du compagnon doit différer de celui du chat.
Éprouvé
45 constats au vert. Trois mutations, trois attrapées : remettre _blank sur les liens internes (6 échecs), neutraliser la détection d'externalité (3 échecs), faire rendre _blank au nom du compagnon (3 échecs).
⚠ Le constat qui manque, et il est nommé : deux onglets et un vrai clic dans un navigateur. Aucun banc PHP ne le remplacera.
4.19.49 — 23 août 2026
Le premier enrôlement du propriétaire fonctionne — il n'avait jamais pu fonctionner, et son banc fabriquait lui-même la commande qui manquait.
Bugs corrigés
- ⚠⚠⚠
on_passMOURAIT À SA PREMIÈRE LIGNE UTILE.find_owner_on_ircappelaitusers, qui n'est pas une commande Eggdrop — le bon nom estuserlist, que le projet écrit partout ailleurs (IriX-sql.tclfait mêmeforeach u [userlist n], exactement le même usage). Conséquence : sur toute installation neuve,/msg IriX pass …rendaitinvalid command name "users", aucun masque n'était posé, aucun compte owner ne naissait. Le correctif du 21/08 sur la forme du masque portait donc sur du code inatteignable.
- ⚠⚠ ET LE BANC ÉTAIT VERT PARCE QU'IL FABRIQUAIT LA COMMANDE MANQUANTE.
test-register.tclchargeait le vraifind_owner_on_irc, puis définissait lui-mêmeproc users {} { … }. Il éprouvait le code dans un monde où la commande absente existait. Son bouchon s'appelle désormaisuserlist. Un bouchon ne doit porter que des noms que l'hôte fournit vraiment.
Comment on l'a su
La production ne pouvait pas le voir : son propriétaire existe depuis le premier jour, donc ce chemin n'y est jamais repris. Il a fallu monter une installation neuve — Ubuntu 24.04, base vide, userfile absent — et y faire un vrai premier enrôlement. Le masque attendu est apparu à 20:58:20 : --HOSTS *!*servus@127.0.0.1.
⚠ Les deux lignes de journal qui prouvent l'enrôlement — « promu owner (premier enrôlement) » et « Active HOSTS DCC » — ne sont pas dans IriX.log mais dans IriX/cmds/IriX-sql.log. Un contrôle sur le seul journal principal aurait conclu que le constat avait échoué.
Éprouvé
Deux mutations, deux attrapées : remettre set users [users] arrête le banc sur l'erreur même de la production ; retirer le * de tête du masque donne 2 échecs sur 24. Restauré, le banc rend 24 vérifications au vert.
4.19.48 — 23 août 2026
Une fin de prêt n'écrit plus qu'une ligne au journal, et le banc du mandat cesse de regarder un bouchon là où il croyait regarder le code.
Bugs corrigés
- ⚠⚠ DEUX LIGNES AU JOURNAL POUR UNE SEULE FIN DE PRÊT.
terminerjournalise — c'est la porte — etbalayerjournalisait aussi, pour marquerexpireplutôt queok. Chacune avait raison séparément ; ensemble elles écrivaient deux fois. Mesuré en base : ids 15 et 16 du journal des prêts, même seconde, sur le mandat qui a servi à constater le point 30. Les expirations d'avant le 14/08 n'en ont qu'une : le doublon est né le jour oùterminers'est mis à journaliser, sans qu'on regarde ce que ses appelants faisaient déjà. balayerpasse désormais l'issue àterminerau lieu de réécrire : une seule ligne, avec la bonne valeur. C'est la règle que la brancheMANDATFINappliquait déjà chez elle — « une porte, une rédaction ».
Évolutions
extrairesait charger une procédure à arguments par défaut. Il prenait la première fermante venue pour la fin de la liste d'arguments — sur{chan motif {par ""}}, c'est celle de l'argument. Le défaut dormait parce que la seule procédure chargée ainsi n'avait aucun argument.
Avertissements
- ⚠⚠ LE BANC BOUCHONNAIT
terminer, et deux mutations du VRAI code le laissaient VERT — lui faire écrireokquoi qu'on lui passe, et lui faire cesser d'écrire. C'est exactement le défaut que le point 30 avait déjà rencontré : le banc bouchonnaitechus, et sa clauseWHERE— là où vivait le défaut — n'était éprouvée par personne. La parade avait été trouvée alors ; elle est appliquée ici au second cas. - ⚠ Le bouchon reste en place : il sert à éprouver l'ordre des gestes sans toucher à une base. Les deux approches se complètent — l'une regarde la porte, l'autre le chemin.
- ⚠⚠ UNE ACCOLADE NUE DANS UN COMMENTAIRE TCL CASSE LA PROCÉDURE QUI LE CONTIENT. « Le corps commence au
{suivant », écrit dans un commentaire, a referméextrairetrente lignes trop tôt — et le message parlait d'un « close-brace » ailleurs. C'est le jumeau du commentaire dans unswitch, que ce dépôt connaît déjà pour avoir coupé le serveur 89 secondes. - ⚠ 213 vérifications au banc du mandat, 6 mutations essayées, 6 attrapées. 41 bancs Tcl au vert.
4.19.47 — 23 août 2026
La file d'installation des robots a enfin un producteur : un bouton au panel. Le service qui la consomme tournait à vide depuis le premier jour.
Créations
botpool_deposer()et l'actionbotpool_demander— le panel dépose une lignepending, le service dédié l'installe au passage suivant de son minuteur, dans les cinq minutes. Grade requis : Admin, le même que décider un prêt de robot. Tranché par SerVuS le 23/08/2026 : un bouton, pas un déclencheur automatique — le parc ne grandit jamais sans quelqu'un devant l'écran, ce qui importe puisque l'exécutant pose de vrais Eggdrop.Web/tests/test-botpool-panel.php— 31 constats. Il garde la jointure, qui était précisément ce que personne n'éprouvait.
Bugs corrigés
- ⚠⚠ LA FILE N'AVAIT AUCUN PRODUCTEUR — point 35, ouvert le 17/08. Les deux seuls
INSERTdu dépôt vivaient dans une procédure Tcl que personne n'appelait, et n'a jamais appelée. Mesuré : la table porte une seule ligne depuis sa création,AUTO_INCREMENTà 2, et cette ligne ne peut pas venir du code — la procédure engendre le nom en prenant la première lettre libre, soitOverOp-A, or la ligne porteOverOp-Tet un pseudo humain là où le code passe « auto ». - ⚠⚠ LE MIROIR PHP N'AVAIT QUE DEUX GARDE-FOUS SUR QUATRE. Il connaissait
plafondetechecs, ignoraitencoursettrop_tot— et personne ne l'avait vu parce qu'il ne servait qu'à AFFICHER un avertissement. Une moitié de garde-fou qui n'a jamais rien gardé ne se remarque pas.encoursest le plus important pour un bouton : sans lui, deux clics déposent deux lignes et l'exécutant pose deux robots. - ⚠ Trois endroits affirmaient au présent une chaîne automatique inexistante : le texte affiché par la page du panel (« OverBot dépose une ligne ici »), et son en-tête qui promettait « cette page NE FAIT RIEN ». Les deux sont corrigés — et l'argument de l'en-tête est conservé, parce qu'il reste juste : il vise deux chemins d'écriture concurrents, or la file n'en avait aucun. Le bouton ne crée pas un second chemin, il crée le premier.
Avertissements
- ⚠⚠ UN BANC A ATTRAPÉ UNE VRAIE ERREUR DE MA MAIN : j'avais supposé une colonne
nickqui n'existe pas — elle s'appellename.test-colonnes.phpcroise chaque colonne citée dans le PHP avec le schéma réel, et il a rougi. Sans lui, la requête aurait levé en production, au premier clic, dans untryqui l'aurait avalée en un message poli et faux. - ⚠ Le banc croise les trois constantes du panel avec celles du robot, et la mutation le vérifie dans les deux sens : changer le plafond d'un côté sans l'autre fait rougir. Deux écritures valent deux vérités — le panel aurait dit « plafond atteint » sur un parc que le robot juge ouvert.
- ⚠ 31 constats, 14 mutations essayées, 14 attrapées. 53 bancs PHP au vert.
- ⚠ Ce qui reste à constater : un vrai clic qui dépose une ligne, et le service qui la ramasse. ⚠⚠ Il posera un vrai robot — c'est le seul constat de ce point qui ne se simule pas.
4.19.46 — 23 août 2026
Les traces d'administration du site reçoivent enfin une date de péremption — 90 jours — et c'est le robot qui les efface.
Créations
::soc::audit::purger— la purge des traces d'administration du site, appelée depuis le scan horaire du robot. Durée et emplacement tranchés par SerVuS le 23/08/2026. ⚠ Horaire et non quotidien parce que c'est là que vivent déjà les purges par ancienneté — événements et sessions.- ⚠ La table appartient au SITE, et c'est le robot qui la purge. Ce n'est pas une bizarrerie : le site n'a aucun mécanisme périodique, quand le robot en tient déjà un, éprouvé, avec son rattrapage. Écrire un second ordonnanceur pour une seule purge aurait coûté plus cher que la purge. Les deux se parlent sans frontière : même fichier de configuration, même compte, même base, même préfixe de table — mesuré.
Évolutions
- La charte annonce désormais 90 jours pour ces traces, là où elle avouait hier que rien ne les purgeait. ✅ L'aveu aura suffi à faire poser la purge — c'était tout son objet : une page publique qui décrit un manque oblige à le combler, là où un point de registre attend.
Avertissements
- ⚠⚠
DELETE … WHERE, JAMAISTRUNCATENIDROP, et un banc le garde. Le plafond de l'ASV compte ses refus dans cette même table, etasv_rate_block()ouvre en grand quand elle est absente — pour ne pas transformer un défaut d'installation en coupure de service. Une purge trop violente désactiverait donc silencieusement la limitation de débit. - ⚠ Aucune interaction possible entre les deux : la fenêtre du plafond est d'une heure, la rétention de 90 jours. 89 jours et 23 heures de marge.
- ⚠ La purge sera INERTE jusqu'en novembre : mesuré le 23/08, la table porte 22 lignes et aucune de plus de seize jours. D'où le comptage avant/après, copié sur
::soc::hist::purger— sans lui, rien ne distinguerait « rien à purger » de « refusé en silence » pendant plus de deux mois. - ⚠⚠ LE BANC DE LA CHARTE A ROUGI SUR DU CODE JUSTE, et il avait raison de le faire. En ajoutant un second
variable JOURSau fichier du robot — 90 pour l'audit, 30 pour l'historique — la résolution du banc prenait la première trouvée : il annonçait « les messages publics : le code dit 90 » sur une purge à 30 jours. Chaque durée est désormais résolue dans son propre namespace, en cherchant la déclaration la plus proche avant leDELETE. - ⚠ C'est la quatrième fois de la journée pour ce motif — le motif plus large que la chose. Il s'est produit deux fois dans le banc du chat, une fois dans celui de la charte ce matin, et une fois dans le même ce soir.
- ⚠ 19 constats à la charte, 52 bancs PHP au vert.
4.19.45 — 23 août 2026
Le champ « Ville » du chat peut se remplir tout seul, si le visiteur le demande — et la position ne quitte jamais sa machine sans avoir été arrondie.
Créations
- La géolocalisation facultative de l'ASV — demandée par SerVuS le 16/08/2026, point 25 du registre. Une case à cocher, jamais cochée par défaut, propose au navigateur de donner la position ; le champ Ville se remplit, et le visiteur peut corriger. C'est la première permission navigateur de ce projet : rien d'autre n'y demandait la position, la caméra ou les notifications.
Web/tests/test-asv-geo.php— 26 constats. Il lit le VRAI fichier de villes, parce que c'est le seul moyen d'éprouver « Lyon rend Lyon ». ⚠ Si le fichier n'a pas été réimporté, ces constats sont sautés, jamais rouges : un clone frais ne doit pas rougir pour une raison qui n'est pas un défaut.- La route
?p=asv-ville, en POST uniquement. ⚠ Apache journalise la ligne de requête entière, paramètres compris : une position enGETfinirait en clair dans les journaux, à côté de l'adresse IP, sans purge écrite.
Évolutions
Config/villes.txtporte trois colonnes de plus — latitude, longitude, population — ajoutées EN FIN. Les insérer avant le rang aurait fait de la troisième colonne une latitude, et « Paris » serait redevenu « París » au WHOIS. Le fichier passe de 2,1 à 5,2 Mo ; le chemin d'entrée du chat n'y perd que ~6 %, les colonnes ajoutées n'étant jamais lues par le contrôle de ville.- L'importeur reprend désormais les noms français depuis le jeu de données par langue de GeoNames (~8 Mo de plus au téléchargement). ⚠ Ce téléchargement est facultatif : s'il échoue, l'import continue avec les noms d'origine. Un réseau capricieux ne doit pas empêcher de refaire une liste de villes.
- Un second garde-fou refuse un import dont les coordonnées seraient absentes ou hors du périmètre. Le premier ne comptait que des lignes : un changement d'ordre des colonnes chez GeoNames aurait produit le bon NOMBRE de villes avec des coordonnées absurdes, et la géolocalisation aurait désigné une commune au hasard.
Bugs corrigés
- ⚠⚠ QUATRE DÉFAUTS TROUVÉS EN ÉPROUVANT, AUCUN EN RELISANT. La première écriture rendait « le lieu le plus proche » — la réponse évidente, et la fausse. Mesurée sur des positions réelles, elle rendait :
| Position | Ce qui sortait |
|---|---|
| Lyon | « Mutualité Préfecture Moncey » (un quartier) |
| Paris | « Paris 04 Hôtel-de-Ville » (un arrondissement) |
| Montréal | « Ville-Marie » (idem) |
| Bruxelles | « Brussels » (le nom anglais) |
| Genève | « Geneva » (idem) |
- Un quartier est toujours plus proche du point que le centre de sa ville. Les quartiers sont désormais écartés à l'import : leur nom reste tapable, mais une position ne peut jamais les désigner. La liste des noms acceptables et la liste des lieux désignables sont deux choses, et c'est le fichier qui porte la différence.
- ⚠⚠ Les arrondissements portent le MÊME code GeoNames que les vraies communes — « Paris 04 Hôtel-de-Ville » est un
PPL, exactement comme « Paris ». Seul le nombre d'habitants les sépare : 27 332 contre 2 138 551. D'où la règle finale : dans un rayon proche, le plus PEUPLÉ l'emporte ; au-delà, le plus proche. - Le nom français existait déjà dans le fichier, mais comme variante — donc jamais choisi par une position. Le premier import avait rencontré ce problème pour la SAISIE en août ; il se reposait ici pour l'AFFICHAGE.
Avertissements
- ⚠ La position est arrondie DEUX FOIS — par le navigateur avant l'envoi, et par le serveur avant l'usage. Deux décimales, soit ~1,1 km : assez pour la bonne commune, trop grossier pour une rue. Le premier arrondi évite que la position précise quitte la machine ; le second garantit qu'elle ne sert à rien de plus fin, même postée à la main. Un contrôle côté navigateur seul ne protège personne.
- ⚠ Rien n'est écrit nulle part : ni base, ni journal, ni session. Les coordonnées traversent la mémoire du processus et disparaissent avec lui.
- ⚠⚠ SIX CONSTATS DU BANC ÉTAIENT CREUX, et la passe de mutation les a désignés un par un. Trois cherchaient un mot que la mutation laissait en place — vider
GEO_EXCLUSn'efface pas « PPLX » du commentaire juste au-dessus. Deux reposaient sur des données de fantaisie qui rendaient le même résultat avec et sans la règle. C'est le troisième banc de la journée à mentir, et le troisième où seule la mutation l'a vu. - ⚠ Le dernier constat non attrapé est documenté comme tel dans le banc : la garde sur les positions impossibles fait double emploi avec le plafond de distance. Elle reste une économie de 140 000 calculs, pas une protection.
- ⚠ 26 constats, 14 mutations essayées, 13 attrapées. 52 bancs PHP au vert.
4.19.44 — 23 août 2026
La charte disait trois choses fausses à ses visiteurs. Elle dit désormais ce que le code fait — et un banc l'y tient.
Bugs corrigés
- ⚠⚠ « Les conversations publiques ne sont pas archivées intégralement » était FAUX. Chaque message écrit dans un salon public du webchat est inséré en base et gardé 30 jours. La charte affirmait l'inverse, et cette archive ne figurait dans aucune ligne de son tableau. SerVuS a tranché pour dire la vérité plutôt que de retirer la fonction : la ligne existe maintenant, avec sa durée et son usage.
- ⚠⚠ L'ASV donné pour conservé « la durée de la connexion » — FAUX aussi. Publiée, la ligne entre dans le réservoir
WHOWASdu serveur : 4 096 entrées relues après le départ, qui n'expirent pas avec le temps mais seulement quand autant d'autres connexions se sont succédé. - ⚠⚠ L'adresse IP ne figurait nulle part dans le tableau des données, alors qu'elle est enregistrée entière à deux endroits. Ce n'était pas un mensonge mais une omission — et une page qui énumère ce qu'on collecte en taisant la donnée la plus identifiante ne vaut pas mieux qu'une page absente. Elle y figure, avec ce qui est vrai : masquée aux autres utilisateurs par le serveur, et une purge qui manque encore côté site, annoncée plutôt que passée sous silence.
- ⚠ Le code affirmait l'inverse, et c'était faux dans l'autre sens : « l'ASV est toujours enregistré et sert la modération », écrit dans
asv.phpet dans la page du chat. Rien ne l'enregistre — la route valide, compose, répond, sort. - Deux durées données pour « limitée » sont désormais chiffrées : 90 jours pour les événements de salon, 30 jours après la fin d'une session.
Évolutions
- La puce sur la publication de l'ASV dit maintenant ce que « visible » veut dire : ces informations peuvent servir à vous rechercher — le réseau sait rendre la liste des personnes ayant publié une ville donnée — et elles restent lisibles après le départ. ⚠ Quelqu'un qui accepte d'être vu n'accepte pas forcément d'être listé.
Créations
Web/tests/test-charte-retention.php— il croise les durées annoncées au visiteur avec celles que le code applique. ⚠ Ce dépôt a déjà établi qu'aucun contrôle automatique n'attrape une affirmation en français ; ce banc ne prétend pas relire la charte. Mais une durée est un nombre, et un nombre se compare — porterJOURS 30à 90 sans toucher à la page la fait désormais rougir. Aucune durée n'est recopiée dans le banc : toutes sont extraites du code à chaque exécution.- ⚠ Son contrôle le plus important n'est pas la comparaison mais l'OMISSION : il énumère les purges du code et signale celles dont la charte ne parle pas. C'est ce qui aurait attrapé le défaut d'origine — lequel n'était pas une valeur fausse, mais une table entière dont personne n'avait parlé.
Avertissements
- ⚠⚠ CE BANC A MENTI TROIS FOIS PENDANT SON ÉCRITURE, et la passe de mutation l'a dit les trois fois. Aucune relecture n'en a vu une seule : 1. VERT sur une valeur fausse — un motif traversant courait de
_sessionsjusqu'au premierINTERVALdu fichier : le banc annonçait « le code dit 90 » sur une purge à 30 jours ; 2. ROUGE sur du code juste — le Tcl échappe ses backticks, le motif en exigeait un nu ; 3. VERT sur une durée changée — il cherchait chaque valeur dans la page entière : « 90 jours », écrit pour les événements, validait la ligne des messages publics. Une valeur trouvée au mauvais endroit valait confirmation. - Les trois sont le même défaut — le motif plus large que la chose — et il s'est produit dans le banc écrit pour attraper des écarts. Une durée se vérifie désormais dans la ligne du tableau qui la concerne, jamais dans la page.
- ⚠ Un point reste ouvert et il est nommé dans la charte : la table d'audit du site garde des adresses IP sans aucune purge, sa migration le dit en toutes lettres. L'annoncer au visiteur oblige à la poser.
- ⚠ 16 constats, 10 mutations essayées, 10 attrapées.
4.19.43 — 23 août 2026
On entre dans le chat SUR PLACE, et ce sont les autres pages qui en sortent : deux onglets au maximum, là où il y en avait trois.
Évolutions
- Le lien « Chat » n'ouvre plus d'onglet. Le modèle tient en une phrase : le chat est la page où l'on est, et tout le reste en sort. Demandé par SerVuS le 23/08/2026, et son compte est l'argument : avec un onglet neuf pour le chat, lire la charte depuis l'accueil laissait trois onglets — accueil, chat, charte. En entrant sur place, il en reste deux, et rien n'est perdu, puisque depuis le chat tout ressort en onglet.
- ⚠ Ce changement n'aurait pas été sûr ce matin. Revenir en arrière depuis le chat détruit la page, donc la conversation — et rien ne l'annonçait. C'est la demande de confirmation, posée quelques heures plus tôt, qui le rend acceptable. Les deux demandes de SerVuS de cette journée ne sont pas indépendantes : la seconde autorise la troisième.
- ⚠⚠ L'onglet du chat SE NOMME LUI-MÊME désormais. Ouvert sur place, il ne reçoit aucun nom du lien qui l'a mené là — et un onglet sans nom ne peut pas être retrouvé par les autres pages, qui en ouvriraient un second. Il se nomme donc à l'ouverture, quelle que soit la façon dont on est arrivé : lien, favori, adresse tapée, ou onglet déjà ouvert avant la mise à jour.
- Le lien « Chat » est désormais la seule exception de la passe de sortie : il mène à la page où l'on est déjà, et le faire sortir ouvrirait un second chat depuis le chat lui-même. ⚠ Les aperçus d'habillage (
?p=chat&logo=…), eux, sortent normalement : la comparaison est exacte, jamais un préfixe.
Suppressions
site_nav_cible()— elle ne rendrait plus que du vide. La règle est maintenant une absence, ce qui la rend plus facile à enfreindre sans y penser : il n'y a plus de fonction à oublier d'appeler, juste un attribut à ne pas ajouter. Un contrôle d'exhaustivité la garde.
Avertissements
- ⚠⚠ Une découpe automatique a emporté DEUX FONCTIONS de trop, parce qu'un ancien bloc de commentaire identique subsistait plus haut dans le fichier et que la recherche a trouvé celui-là. Rattrapé par le banc — fonction introuvable à l'exécution — et non par la relecture. Un repère de découpe doit être unique, et se vérifier comme tel.
- ⚠ Un intitulé de banc avait survécu à la règle qu'il décrit : il disait encore « seul le lien du chat vise un autre onglet » alors que plus aucun lien n'en vise. Il était vert, et il mentait poliment. Resserré.
- ⚠ 40 constats, 22 mutations essayées, 22 attrapées.
4.19.42 — 23 août 2026
Le verrou peut survivre à son onglet quinze secondes : pendant ce temps, revenir au chat ouvrait une page blanche.
Bugs corrigés
- ⚠⚠ Une page VIERGE pouvait s'ouvrir à la place du chat. Le verrou porte une échéance de quinze secondes — c'est ce qui empêche un onglet tué brutalement d'interdire toute reconnexion pour toujours. Mais pendant cette fenêtre, le verrou dit « conversation en cours » alors que l'onglet n'existe plus : la bascule ne retrouvait rien et ouvrait un onglet blanc. Le visiteur cliquait « Chat » et obtenait une page vide, sans rien pour le lui expliquer — pire que le défaut d'origine, qui lui donnait au moins un chat.
- L'onglet vierge est reconnu à son adresse et refermé aussitôt, sans empêcher le lien : il ouvre alors le chat, ce qui est exactement ce qu'il faut faire quand il n'y a plus de conversation à rejoindre.
- ⚠ Ce cas n'a pas été trouvé en relisant, mais en mesurant la page servie. Le verrou et l'onglet sont deux choses, et l'un peut mentir sur l'autre — c'est la même leçon que le reflet
_onlinedu bot, sur un autre sujet.
Avertissements
- ⚠⚠ Un constat du banc était CREUX, pour la seconde fois dans la même séance, et la mutation l'a dit les deux fois. Il cherchait les lignes qui referment l'onglet vierge — or elles restent écrites quand on neutralise la condition qui les commande. Un constat doit exiger la CONDITION, pas ses effets. Avoir appris la leçon trois constats plus haut n'a pas suffi à me la rappeler : seule la passe de mutation l'a fait.
- ⚠ 12 mutations essayées, 12 attrapées, 36 constats.
4.19.41 — 23 août 2026
Revenir au chat n'y touche plus, et le quitter se demande. La version précédente avait introduit une régression : elle est corrigée le jour même.
Bugs corrigés
- ⚠⚠ RÉGRESSION DE LA 4.19.40, trouvée par SerVuS en la mesurant. Le lien « Chat » visait un onglet nommé pour éviter d'en ouvrir deux — mais un lien qui vise un onglet existant le NAVIGUE, donc le recharge, donc coupe la conversation : « ça reste sur l'onglet actif mais ça ferme la connexion et revient au panneau de pseudo ». Le remède était pire que le mal : avant le nom, un second onglet s'ouvrait et le premier restait vivant.
- Le clic est désormais intercepté : quand une conversation tourne, la page bascule sur l'onglet du chat sans le naviguer. Mesuré avant d'être écrit —
window.open('', nom), URL vide, retrouve l'onglet nommé sans y toucher : un marqueur posé en mémoire y survit et l'adresse ne bouge pas. Avec une URL, le contexte est navigué. C'est un caractère, et il porte toute la différence. - ⚠ La bascule est gardée par le verrou : sans conversation en cours, le lien fait son travail ordinaire. Sur un nom qui n'existe pas,
window.openouvrirait une fenêtre vide — pire que tout. - ⚠⚠
beforeunloadenvoyait leQUITavant toute question, ce qui rendait impossible d'en poser une : répondre « rester » aurait gardé la page sur une connexion déjà close par nous-mêmes, fil affiché, mort, et rien pour le dire. Le départ n'est certain qu'une fois la question tranchée : leQUITdescend danspagehide, qui est de surcroît plus fiable sur mobile.
Évolutions
- Quitter une conversation se demande. Fermer ou recharger la page du chat pendant une connexion déclenche l'avertissement du navigateur. Demandé par SerVuS le 23/08/2026. ⚠ Le texte affiché n'est pas le nôtre : depuis 2016 les navigateurs imposent le leur. ⚠ Et la question ne paraît que si le visiteur a interagi avec la page — règle des navigateurs contre les pièges à fermeture. Ce n'est donc pas une garde, c'est un garde-fou.
- Le verrou du chat est décrit à UN seul endroit, en PHP. Il était écrit en dur dans le client, et il faut désormais le lire depuis deux endroits : la page du chat, qui le pose, et toutes les autres, qui doivent savoir s'il y a une conversation en cours avant de laisser un lien la recharger.
Avertissements
- ⚠ Une piste a été écartée : griser le bouton « Chat » tant qu'une conversation tourne. Elle aurait lu la même clé que le verrou — donc aucune protection de plus — et un verrou resté coincé serait devenu un bouton mort et muet, là où le refus actuel dit au moins quoi faire.
- ⚠⚠ Trois constats du banc étaient CREUX, et la passe de mutation les a trouvés — pas la relecture. L'un cherchait le nom d'une fonction, qui reste écrit même quand on retire l'appel ; deux autres interrogeaient une page produite par la fonction même qu'ils vérifiaient, donc ne pouvaient jamais rougir. Le vrai risque était ailleurs : changer la clé côté serveur sans changer la valeur de repli du client ferait lire au site une clé que le chat n'écrit pas — aucune erreur, aucun rouge, juste la panne. C'est ce que le banc mesure maintenant.
- ⚠ 11 mutations essayées, 11 attrapées, dont la régression exacte ci-dessus.
4.19.40 — 23 août 2026
Il ne peut plus s'ouvrir deux chats : le lien vise un onglet nommé, et le navigateur ramène à la conversation en cours au lieu d'en ouvrir une seconde.
Évolutions
- Le lien « Chat » vise un onglet NOMMÉ au lieu d'un onglet anonyme. Un lien vers un nom déjà pris ne crée rien : le navigateur retrouve l'onglet qui le porte et bascule dessus. Depuis n'importe quelle page du site, cliquer « Chat » ramène donc à la conversation en cours. Demandé par SerVuS le 23/08/2026 : « empêcher d'ouvrir plus de 1 page Chat ».
- ⚠ Ce mécanisme ne tient AUCUN état, et c'est ce qui le rend préférable : c'est le navigateur qui sait quels onglets existent. Rien à poser, rien à faire expirer, rien qui puisse rester coincé — et l'onglet fermé en rouvre un neuf, sans délai d'attente.
- ⚠ Le nom est dérivé de l'identité du réseau, jamais écrit en dur. Les noms de fenêtre ne sont pas cloisonnés par site : deux instances ouvertes dans le même navigateur se seraient partagé un nom fixe, et la seconde aurait volé l'onglet de la première — en coupant sa conversation, soit le défaut même que ce mécanisme corrige.
Bugs corrigés
- ⚠ Deux entrées vers le chat contournaient la règle — la carte de la page d'erreur et un lien de la page de contact ouvraient le chat dans l'onglet courant, quand le menu et le pied faisaient l'inverse. Trois écritures pour la même chose, donc trois comportements. Les deux passent désormais par la même fonction.
Avertissements
- ⚠⚠
rel="noopener"est RETIRÉ du lien du chat, et ce n'est pas un oubli. La règle du HTML est explicite : sousnoopener, tout nom d'onglet autre que_top,_selfet_parentest traité comme un onglet anonyme. Le garder annulerait le nom en silence — on croirait le correctif posé alors qu'il n'agirait pas. Le laisser tomber est sans danger sur ce lien et sur aucun autre : il pointe vers notre propre site, même origine, alors que la protection vise une page tierce qui réécriraitwindow.opener. Tous les autres liens ouverts en onglet le gardent. - ⚠ Un banc garde précisément cette régression : rajouter le
noopener« oublié » — ce qu'un relecteur bien intentionné fera un jour — fait rougir. Sans lui, le chat se serait remis à s'ouvrir en double sans qu'une ligne ne le dise. - ⚠ Une piste a été écartée et il faut dire pourquoi : griser le bouton « Chat » tant qu'une conversation tourne. Elle aurait lu la même clé que le verrou — donc aucune protection de plus, seulement affichée plus tôt — et un verrou resté coincé serait devenu un bouton mort et muet, là où le refus actuel dit au moins quoi faire. Elle aurait de surcroît exigé du JavaScript sur toutes les pages du site, qui n'en chargent aucune aujourd'hui.
4.19.39 — 23 août 2026
Le chat ne se laisse plus quitter en place : depuis sa page, tout lien s'ouvre ailleurs et la conversation reste. Et un fichier d'état oublié d'une liste cesse de faire échouer chaque vérification de mise à jour.
Créations
Web/tests/test-chat-onglet.php— rien ne gardait le correctif qui fait sortir le chat dans son propre onglet : aucune vérification du projet ne nommait cet attribut, et il pouvait disparaître d'une ligne sans que rien ne le signale. Le banc rend la page et relit les liens qui en sortent, plutôt que de chercher un mot dans un fichier — trouver le mot prouverait qu'on l'a écrit, jamais qu'il agit. Huit façons de casser le correctif ont été essayées sur lui : il les attrape toutes.
Bugs corrigés
- ⚠⚠ Depuis la page du chat, chaque lien du site coupait la conversation. Ouvrir le chat dans son propre onglet ne protegeait que le premier clic. Une fois dedans, la barre de navigation, le menu replié, les quatre groupes du pied de page, le logo et trois liens du corps restaient des liens ordinaires — plus de vingt en tout. Le visiteur qui cliquait « Charte » depuis le chat perdait exactement ce que le correctif précédent prétendait sauver, et la moitié manquante n'avait jamais été nommée nulle part.
- ⚠ L'attribut n'est pas écrit vingt fois, et c'est le fond du correctif : le pied porte ses liens en dur, la navigation les produit en boucle, la page du chat en ajoute trois. Trois écritures valent trois vérités, et le lien qu'on ajoutera dans six mois n'en connaîtrait aucune. La page du chat relit désormais sa propre sortie en une seule passe, qui ne peut rien oublier — y compris ce qui n'est pas encore écrit.
- ⚠⚠
Runtime/state.default.jsonne figurait dans aucune des deux listes de la mise à jour. Surveillé sans être distribué, il était réclamé au dépôt amont, qui ne l'a pas : échec permanent à chaque vérification, donc une alerte de plus à chaque passage. C'est la panne exacte deConfig/travaux.jsonle 31/07/2026 et deConfig/overop.jsonle 20/08 — la troisième fois pour le même motif. Il porte les chemins absolus d'une machine et n'a jamais rien à faire en amont. - ⚠ Ce dernier défaut ne se voyait pas sur un poste de travail : le fichier n'existe pas dans un clone, c'est l'installateur qui l'écrit. La vérification était donc verte là où l'on travaille et rouge là où le fichier vit.
Modifications
- Les droits de
Runtime/state.default.jsonont été alignés sur ceux de son jumeau sur l'installation en service. L'installateur les pose déjà ainsi depuis le 20/08 ; le fichier en place avait été écrit avant ce correctif et n'en avait jamais bénéficié.
Avertissements
- ⚠ Le point du registre qui portait ce défaut décrivait encore l'état d'avant le 16/08/2026 : il réclamait trois choses déjà écrites depuis, et passait sous silence la seule qui manquait vraiment. Un point du registre se remesure au moment où l'on travaille dessus.
4.19.38 — 21 août 2026
L'installateur pose enfin les fichiers d'Apache, et le compte propriétaire du bot ne naît plus avec une porte ouverte.
Créations
IriX/tests/test-register.tcl— le module qui enrôle le propriétaire du bot n'avait aucune vérification automatique, alors que c'est le seul qui écrive le fichier des comptes : c'est lui qui crée le compte, lui accorde les pleins pouvoirs, et écrit le masque par lequel le bot le reconnaîtra.
Évolutions
- L'écran « Services système » de l'installateur pose désormais les cinq fichiers d'Apache — action 4. Ils vont à trois endroits différents : les hôtes virtuels d'un côté, le fichier des ports d'écoute à la racine d'Apache, la page de maintenance dans un dossier servi.
- ⚠ Ils sont posés sans être activés, et c'est la décision qui commande tout le reste : activer l'hôte virtuel sécurisé avant d'avoir obtenu un certificat empêche Apache de démarrer — c'est le site entier qui tombe, pas seulement cette entrée. L'activation reste un geste de l'exploitant, et l'installateur affiche la marche à suivre plutôt que de la faire à sa place.
- ⚠ Le fichier des ports d'écoute n'appartient pas au projet : il vient du paquet Debian, et le poser l'écrase. L'installateur en conserve donc l'original — et deux protections l'entourent, parce qu'une seule ne suffisait pas : ne jamais reprendre la sauvegarde au second passage, et ne jamais sauvegarder notre propre fichier en le prenant pour l'original. Ce second cas a été trouvé en mesurant l'installation en service.
- Le désinstalleur a été mis en accord : il ne supprime jamais ce fichier — il le restaure, ou dit qu'il ne le peut pas. Son rechargement d'Apache, jusque-là inconditionnel, passe maintenant par une vérification préalable.
Bugs corrigés
- ⚠⚠ Le compte propriétaire d'IriX naissait avec un masque qui n'exigeait aucun identifiant : toute connexion venue du même domaine réseau était reconnue comme le propriétaire du bot. Sur la machine de développement ce domaine était celui du réseau local ; chez quelqu'un d'autre, c'eût été celui de son fournisseur d'accès — des milliers d'abonnés. Le masque exige désormais l'identifiant de celui qui s'enrôle.
- ⚠ Ce défaut ne se voit pas sur une installation en service : le chemin qui pose ce masque ne s'exécute qu'au tout premier enrôlement. Il ne pouvait être trouvé qu'en se demandant ce que reçoit une installation neuve.
Avertissements
- ⚠ Le masque déjà posé sur l'installation en service n'est pas effacé par cette version — un correctif empêche d'en fabriquer un nouveau, il ne nettoie pas l'existant. Il a été retiré à la main le jour même, le bot arrêté le temps du geste : il garde son fichier de comptes en mémoire et le réécrit en s'arrêtant, si bien qu'une modification à chaud aurait été perdue.
- ⚠ La pose des fichiers système n'a pas encore été lancée sur une machine réelle — écrire à ces emplacements demande un mot de passe. Les vérifications automatiques l'exécutent sur une arborescence factice.
4.19.37 — 21 août 2026
Les grades du réseau et ceux du salon ne se ressemblent plus.
Évolutions
- Le chat affiche deux choses qui n'ont rien à voir : le badge « owner » désigne un rôle sur le réseau, tandis que le « ~ » devant un pseudo désigne le propriétaire du salon. On peut être l'un sans être l'autre — et jusqu'ici les deux portaient la même couleur, côte à côte à l'écran.
- Les badges de réseau deviennent des pastilles à contour, là où ceux du salon restent pleins et carrés. La règle se lit sans qu'on ait à l'apprendre : plein = ici, dans ce salon ; contour = ailleurs, sur le réseau.
- ⚠ Les symboles
~,@,+ne changent pas. Ils sont la convention de tous les clients IRC : les modifier pour lever une ambiguïté qui nous est propre ferait payer à l'usage commun une confusion qui nous appartient. - ⚠ Deux repères plutôt qu'un — la forme et le remplissage — parce qu'un seul se perd pour qui distingue mal les nuances de couleur, ou sur un écran mal réglé.
Avertissements
- ⚠ Cette distinction tient à l'ordre des règles de style : la mise en forme du badge de réseau annule le fond posé plus haut, et c'est la dernière écrite qui l'emporte. La déplacer rendrait les deux familles identiques sans provoquer la moindre erreur. Une vérification automatique garde désormais cet ordre — elle a été éprouvée en le cassant exprès.
4.19.36 — 21 août 2026
La liste des pseudos réservés se replie quand elle devient longue.
Évolutions
- Le panneau « Pseudos réservés » du chat affichait tous les comptes du réseau, sans aucune limite. Avec quelques dizaines de pseudos enregistrés, il aurait rempli toute la colonne.
- Au-delà de dix comptes, il ne montre désormais que ceux sur qui on peut vouloir agir tout de suite : un grade, un statut d'opérateur, ou une présence en ligne. Les autres passent derrière un « N autres pseudos réservés » qu'on déplie d'un clic.
- ⚠ En dessous de dix, rien ne change : la liste s'affiche entière. Un dépliant qui cache trois lignes coûte un clic pour rien.
- ⚠ Aucun compte n'est retiré — ils sont repliés, pas écartés. La liste complète continue de servir à savoir si votre propre pseudo est réservé, ce qui décide du bouton « S'identifier » ou « Réserver ce pseudo ».
Bugs corrigés
- ⚠⚠ Le panneau pouvait rester entièrement vide. Le classement des pseudos s'exécutait avant l'écart des entrées incomplètes : il suffisait d'une seule ligne mal formée pour que le classement échoue et que plus rien ne s'affiche. Le code se protégeait déjà de ces lignes, mais trop tard dans l'ordre des opérations. Aucun symptôme ne le montrait tant que la source reste saine ; c'est la campagne d'essai neuve qui l'a trouvé, en lui donnant exprès une liste abîmée.
Créations
- Une campagne d'essai pour ce panneau — il n'en avait aucune. Elle exécute la vraie fonction du fichier envoyé au navigateur plutôt que d'en vérifier une copie, et garde notamment qu'aucun compte ne disparaît du calcul quand il est replié.
Avertissements
- ⚠ Le badge « owner » de ce panneau désigne un rôle sur le réseau, tandis que le « ~ » de la liste des présents désigne le propriétaire du salon. Les deux portent la même couleur et se côtoient à l'écran. Le survol de chaque badge le précise désormais, faute de pouvoir le dire en trois lettres — une distinction visible reste à trancher.
- ⚠ Le badge « oper » indique un état de session, remis à zéro chaque nuit, et non un rôle permanent. Le survol le dit également.
4.19.35 — 21 août 2026
Le grade s'affiche devant le pseudo, dans la conversation du chat.
Évolutions
- Dans le fil du webchat, chaque pseudo est désormais précédé de son grade —
~,&,@,%ou+— dans la couleur qui lui correspond déjà dans la liste des présents. On voit d'un coup d'œil qui parle avec quel rôle, sans aller chercher dans la colonne de droite. - ⚠ Rien n'est recalculé pour cela : la liste des présents était déjà tenue à jour par le serveur (arrivées, départs, changements de mode et de pseudo). Une seconde règle de grade aurait divergé de la première au premier changement de mode.
- Les lignes d'historique, elles, n'auront jamais de grade : l'historique ne conserve que le pseudo. Un grade d'aujourd'hui affiché sur une ligne d'hier serait pire que pas de grade du tout.
Bugs corrigés
- ⚠⚠ Les grades du chat étaient écrits en dur dans le code du navigateur au lieu d'être lus dans ce que le serveur annonce à la connexion. Cela ne se voyait pas — le serveur annonce exactement ce qui était écrit — mais le jour où un grade change ou s'ajoute, le chat aurait affiché les autres de travers, sans rien signaler. Il lit désormais l'annonce du serveur, comme le fait déjà le client de référence du projet.
Créations
- Une campagne d'essai pour ces grades. Elle extrait la vraie fonction du fichier envoyé au navigateur et l'exécute, plutôt que d'en vérifier une copie réécrite : une copie ne prouverait que la copie. Elle éprouve un barème différent de celui d'Overtchat — c'est le seul cas où l'ancien défaut se voyait.
Erreurs
- ⚠ Deux vérifications de cette campagne annonçaient « ok » sans rien vérifier. Elles comparaient une valeur absente à une valeur absente, ce que le passage d'un langage à l'autre rendait indistinguable. Repérées seulement parce qu'un avertissement s'affichait à côté. Corrigées, et une garde refuse désormais toute comparaison dont une valeur manque.
- ⚠ Une vérification du site qui contrôle que chaque classe de style existe bien laissait passer une classe sur deux dans un cas précis. Elle restait verte même en supprimant la règle correspondante. Corrigée, puis éprouvée en cassant le style exprès.
4.19.34 — 20 août 2026
L'assistant d'installation prend les couleurs du site.
Évolutions
- Le panel du terminal affichait huit couleurs génériques, sans rapport avec le reste du projet. Il reprend désormais la palette exacte du panel web — le turquoise du réseau, les mêmes verts, ambres et rouges, les mêmes gris. Les deux écrans se reconnaissent au premier coup d'œil.
- ⚠ Un repli est prévu : sur un terminal qui n'annonce pas les couleurs étendues — console de secours, connexion distante sommaire, tâche automatique —, l'ancien jeu de huit couleurs reprend la main. Sans lui, l'affichage se remplirait de codes illisibles.
Créations
- Une campagne qui croise les deux sources. Les couleurs sont forcément recopiées — un script ne peut pas lire une feuille de style au démarrage — et une copie finit toujours par diverger. La campagne refuse qu'elle se périme : elle rougit dès qu'une teinte change d'un côté sans l'autre.
4.19.33 — 20 août 2026
Deux fichiers de configuration naissaient lisibles par tout le monde.
Bugs corrigés
- Deux fichiers d'état étaient créés sans protection, alors que les cinq autres du même dossier sont réservés à leur propriétaire. L'un d'eux ne contient rien de sensible aujourd'hui — mais il portera demain le propriétaire, le réseau et l'administrateur de chaque robot prêté. La protection se pose avant que le contenu la mérite.
- Un logiciel absent était annoncé comme une panne. Quand un composant optionnel n'est pas installé sur la machine, le programme disait « pose refusée » — ce qui envoie chercher un problème d'autorisations qui n'existe pas. Il l'explique désormais, sans erreur, et ne crée pas le dossier correspondant : un dossier vide ferait croire à une installation.
Avertissements
- ⚠ Ce contrôle a été fait à la demande de l'exploitant, après un large parcours des options. Les données étaient saines : tous les fichiers valides, rien de perdu, rien de corrompu. Ce sont trois défauts de forme qui ont été trouvés, pas une avarie.
4.19.32 — 20 août 2026
Critique
La surveillance installée ce matin n'aurait pas eu le droit de relancer le bot.
Bugs corrigés
- L'installateur pose enfin les autorisations système. Trois fichiers que le programme savait pourtant désinstaller n'étaient jamais installés : les autorisations qui permettent de redémarrer les services, leur équivalent pour le panel web, et la rotation des journaux du serveur.
- ⚠ Le premier est le plus grave, et sa propre documentation le disait déjà : sans lui, la surveillance livrée le matin même constate une panne sans pouvoir y remédier. Sur une installation neuve, elle voyait le bot mourir et n'avait pas le droit de le relancer — sans que rien ne le signale.
- La rotation manquante laissait par ailleurs les journaux du serveur grossir sans limite, ce qui est l'une des pistes d'une panne connue.
Évolutions
- Le fichier d'autorisations est contrôlé AVANT d'être installé, jamais après. C'est la seule pose de ce chantier qui puisse casser autre chose que le projet : un fichier fautif à cet endroit peut rendre l'élévation de privilèges inutilisable sur toute la machine. S'il ne passe pas son propre contrôle, il n'est pas posé — et les autres le sont quand même.
- La liste porte des dossiers, pas des fichiers : ajouter un fichier dans l'un d'eux est couvert sans que personne ait à s'en souvenir.
Avertissements
- ⚠ Les fichiers de configuration du serveur web restent à part, et volontairement : leurs cinq fichiers ont trois destinations différentes, et les activer demande un rechargement qui peut couper le site. Cela reste ouvert au registre.
4.19.31 — 20 août 2026
Reposer les contrôles d'intégrité devient un geste que l'on décide.
Créations
- Depuis la version précédente, la vérification d'intégrité refuse quand la référence manque, au lieu d'enregistrer ce qu'elle trouve. C'était le correctif — mais il laissait une question sans réponse, que le déplacement du fichier a rendue concrète en une minute : plus aucune référence n'existait, et rien ne permettait de repartir.
- Une entrée du menu permet donc de poser les références, délibérément. Elle annonce franchement ce qu'elle fait : elle adopte les fichiers tels qu'ils sont. À n'utiliser que sur une installation qu'on sait saine — sur une machine soupçonnée d'altération, elle graverait l'altération.
- ⚠ Ce n'est volontairement pas automatique : reposer tout seul serait exactement le défaut que la version précédente vient de corriger.
Modifications
- La liste des fichiers contrôlés n'existe plus qu'à un seul endroit, partagé par la vérification et par la pose. Elle était écrite en double, et c'est ainsi qu'un fichier se retrouvait contrôlé pour rien depuis des semaines : enregistré d'un côté, absent de la liste de l'autre.
4.19.30 — 20 août 2026
Critique
La mémoire de l'installation vivait dans un dossier que la machine efface à chaque redémarrage.
Bugs corrigés
- Le fichier qui se souvient de l'installation a été déplacé. Il vivait dans
/tmp, un emplacement vidé à chaque redémarrage de la machine et nettoyé automatiquement au bout de dix jours. Il était absent au moment de la correction. Or il ne contient pas seulement les empreintes de contrôle des fichiers de configuration : il porte aussi la trace que l'installation a eu lieu, la liste des dossiers à créer, la langue, le nom du projet et la liste des modules à déployer. Sa disparition faisait donc croire au programme qu'il n'avait jamais été installé. - La vérification d'intégrité validait ce qu'elle aurait dû dénoncer. Quand la référence manquait, elle enregistrait les fichiers tels qu'ils étaient, quels qu'ils soient, et les déclarait sains au passage suivant. Elle refuse désormais, et le dit. Une détection qui ne détecte pas est pire que pas de détection : elle rassure.
- Le résultat de la vérification n'arrivait jamais nulle part. Elle annonçait « tout va bien » même si les cinq fichiers étaient altérés. Le résultat remonte maintenant, et distingue trois cas : altéré, sans référence, conforme.
- Un sixième fichier était contrôlé pour rien. Son empreinte était soigneusement enregistrée à chaque changement, et jamais comparée à quoi que ce soit. Elle l'est.
Créations
- Une campagne de vérification pour ce mécanisme, qui n'en avait aucune. Elle exécute réellement le code et a été éprouvée en le cassant sur quatre points.
Avertissements
- ⚠ Les trois défauts de logique ont été corrigés en même temps que le déplacement, et il le fallait : un fichier qui ne disparaît plus les aurait figés au lieu de les révéler. Le mécanisme aurait paru sain simplement parce qu'il ne se réinitialisait plus.
4.19.29 — 20 août 2026
Une installation neuve laissait quatorze services au repos, sans que rien ne le dise.
Créations
- L'assistant d'installation pose désormais les services système. Il ne le faisait pas : le programme livrait quatorze définitions de services, et aucune n'était installée. Sur cette machine tout fonctionnait, parce qu'elles y avaient été posées à la main au fil des mois — mais sur une installation neuve, les sauvegardes ne tournaient pas, les opérateurs du panel ne s'appliquaient pas, les robots ne se posaient pas, et la surveillance du bot ne surveillait rien.
- Une sixième étape apparaît dans l'assistant, et elle se repasse autant de fois qu'on veut — après avoir ajouté un composant, ou posé un mot de passe qui manquait.
Évolutions
- On installe tout, on ne met en marche que ce qui peut marcher. Mettre en marche un service dont il manque un prérequis produirait un service en panne dès le premier jour, et un service en panne est un service qu'on apprend à ignorer. Chaque définition déclare elle-même ce qui lui est nécessaire ; l'assistant le lit, et dit ce qu'il laisse au repos et pourquoi.
- Les chemins et le compte d'exécution sont adaptés à l'installation réelle.
Avertissements
- ⚠ Ce qui reste : la pose n'a pas encore été constatée sur une machine neuve. Elle est vérifiée par une campagne qui l'exécute pour de vrai sur un faux système de fichiers — mais un essai n'est pas une installation.
- ⚠ Quatre autres familles de fichiers système restent dans le même cas : le programme sait les retirer, il ne sait toujours pas les poser.
4.19.28 — 20 août 2026
Le bot ne pouvait pas prévenir d'une panne qui l'empêchait justement de prévenir.
Évolutions
- L'envoi des courriels d'alerte reposait sur un seul mécanisme interne, et ce mécanisme s'arrête net quand le bot sature — c'est-à-dire exactement pendant la panne qu'il devrait annoncer. Un second chemin a été ajouté, déclenché par l'horloge et non par ce qui sature : toutes les heures, il expédie ce qui serait resté en attente.
- Quand ce second chemin rattrape réellement quelque chose, il l'écrit dans les journaux. Un filet de sécurité silencieux ne se vérifie pas, et un mécanisme qu'on ne peut pas vérifier finit par ne plus rien garantir.
Avertissements
- ⚠ Ce n'est qu'un filet. Le chemin fiable reste la surveillance extérieure installée la veille, qui vit hors du bot et ne partage aucune de ses pannes. Un bot assez atteint pour perdre ses minuteries peut l'être assez pour perdre son horloge : la fenêtre est réduite, pas fermée.
4.19.27 — 20 août 2026
Critique
La surveillance n'alertait qu'une seule fois, puis se taisait pour toujours.
Bugs corrigés
- Une panne qui revient ne prévenait plus personne. La veille extérieure sur le bot laisse une alerte en base et envoie un courriel. Mais l'alerte restait ouverte une fois la panne finie, et une alerte déjà ouverte ne redéclenche pas d'envoi : la deuxième panne, la troisième et toutes les suivantes se passaient en silence, jusqu'à ce qu'un administrateur pense à la ranger à la main. Les pannes des 18 et 19 août étaient précisément des pannes répétées.
- La surveillance signale désormais qu'elle est elle-même empêchée. Quand elle constate une panne sans pouvoir l'enregistrer ni l'annoncer — base injoignable —, elle le dit au système au lieu de se déclarer en bon état.
Évolutions
- Quand la cause d'une alerte a disparu, la surveillance referme l'alerte elle-même, en signant son geste. L'historique du panel distingue donc ce qu'une machine a refermé de ce qu'un administrateur a lu et acquitté.
Créations
- Une campagne de vérification pour ce mécanisme, éprouvée en cassant volontairement le code sur six points pour s'assurer qu'elle les voit.
Avertissements
- ⚠ Ce défaut n'a été trouvé ni par une relecture ni par une campagne : trois campagnes étaient au vert. Il est sorti d'un essai réel où le service a été coupé deux fois au lieu d'une.
4.19.26 — 19 août 2026
La page d'accueil annonçait quatre serveurs compatibles. Il y en a six.
Erreurs
- Le chiffre était resté en arrière. Deux serveurs ont été ajoutés depuis qu'il a été écrit, et personne ne l'a repris — alors que la page « Serveurs compatibles », à un clic de là, en annonçait bien six en toutes lettres. Deux pages du même site se contredisaient, et c'est la plus lue qui avait tort.
- Le tableau des services était recopié ligne par ligne, alors que la page des commandes dérive le sien de la même source depuis toujours. C'est exactement le défaut qu'un outil avait été écrit pour éliminer sur la page voisine — il subsistait ici.
Modifications
- Tout vient désormais de sa source : le nombre de serveurs, celui des services, le tableau des pseudo-clients, et les deux titres qui les écrivent en toutes lettres. Rien à remettre à jour au prochain ajout.
- ⚠ Une seule chose reste écrite : le verbe de l'exemple de commande, qui illustre sans rien décrire. Le nom du service, lui, vient de la source — pour qu'un service renommé ne laisse pas derrière lui un exemple qui ment.
4.19.25 — 19 août 2026
Critique
La panne qui privait le réseau de tous ses services est CONSTATÉE fermée : trois heures et quart de marche, sans un seul incident.
Évolutions
- Le défaut le plus grave du projet est refermé, et constaté — pas seulement corrigé. Quarante relevés toutes les cinq minutes, de 11 h 41 à 15 h 01 : aucun refus de minuteur, et une mémoire qui n'a pas varié d'un kilo-octet pendant les deux heures quarante-cinq dernières.
- Le chiffre qui tranche : la même mesure, sur la version malade, montrait cent soixante mégaoctets gagnés en trente et une minutes. Ici : moins d'un mégaoctet en trois heures et quart.
- ⚠ La durée comptait plus que la platitude. La fuite était exponentielle, donc invisible au début : une courbe plate pendant vingt minutes n'aurait rien prouvé. Les deux pannes de la veille se déclaraient à soixante-dix-sept et cent deux minutes — la veille a tenu cent quatre-vingt-quinze.
Avertissements
- ⚠⚠ Ce qui rendait ces pannes MUETTES n'est pas corrigé, et fait désormais l'objet d'un point à part. L'alerte du bot emprunte le même mécanisme que celui qui tombe en panne : elle se tait précisément quand il faudrait qu'elle parle. La surveillance extérieure qui répond à ce défaut est écrite et éprouvée, mais pas encore mise en service — c'est un geste d'exploitation.
- ⚠ Le plafond de taille des journaux, lui, n'agira qu'au prochain redémarrage du bot.
4.19.24 — 19 août 2026
Trois écarts du site, trouvés par un contrôle de coordination d'ensemble.
Erreurs
- ⚠⚠ Le point d'état public publiait plus que ce qu'il annonçait. Il servait aussi l'état réel des services, sans le délai d'attente qui existe justement pour ne pas signaler au visiteur une perturbation de moins d'une minute — une reconnexion, un redémarrage de service, un hoquet. Le mécanisme était contourné par la porte même qu'il devait protéger, et l'instant de départ de chaque perturbation était donné avec. Aucun consommateur ne s'en servait : la page d'accueil, seule appelante, ne lit que l'état publiable. Ces deux informations restent indispensables au cache, qui les relit d'un cycle à l'autre — elles vivent donc dans le fichier, plus dans la réponse.
- Un membre qui oubliait son mot de passe ne pouvait pas le retrouver. La page de réinitialisation existe depuis longtemps, mais aucune page du site n'y menait : ni le menu, ni le pied de page, ni l'inscription. Son seul lien vivait derrière le bouton « Administration », là où un membre ne va jamais — alors que le formulaire d'inscription annonce que l'adresse courriel « sert à retrouver l'accès en cas d'oubli ». Le lien est désormais là où la promesse est faite.
- Un commentaire du routeur en contredisait un autre, quinze lignes plus loin, sur la place d'une page dans le menu.
Créations
- Un contrôle sur le contenu de la réponse publique, et non plus seulement sur l'existence de la route. Le contrôle existant le disait lui-même : il gardait la position, jamais ce qui sortait. ⚠ Cassé exprès de deux façons avant d'être retenu.
4.19.23 — 19 août 2026
Le bandeau d'état du panel se renommait tout seul quelques secondes après l'ouverture de chaque page.
Erreurs
- La première pastille du bandeau affichait « IRCd », puis se changeait d'elle-même en « Overtchat-Hybrid » au premier rafraîchissement — quinze secondes plus tard, ou immédiatement en revenant sur l'onglet. Le bandeau s'élargissait au passage. C'était visible sur toutes les pages d'administration.
- La cause : la même table de noms courts était écrite deux fois dans le même fichier — une fois pour l'affichage initial, une fois pour le rafraîchissement — et les deux avaient divergé. Le nom du serveur en service manquait dans la seconde.
- ⚠ Aucun contrôle automatique ne lisait cette seconde table. C'est par là que le défaut est passé, et il y serait resté.
Modifications
- Il n'y a plus qu'une table, et le rafraîchissement la reçoit au lieu de la redire. C'est le procédé déjà employé ailleurs sur le site pour les libellés d'état.
Créations
- Un contrôle qui n'accepte pas deux listes. ⚠ Il ne compare pas les deux pour vérifier qu'elles concordent — il exige qu'il n'y en ait qu'une. Comparer les aurait laissées exister, et se serait contenté de crier le jour où elles divergent à nouveau.
- ⚠ Cassé exprès de deux façons avant d'être retenu, en vérifiant à chaque fois que la modification avait bien été appliquée.
4.19.22 — 19 août 2026
Un défaut de sécurité ouvert au registre : la vérification d'intégrité peut être remise à zéro par un simple redémarrage.
Erreurs
- La référence à laquelle les fichiers de configuration sont comparés vit dans un répertoire temporaire. Elle disparaît à chaque redémarrage de la machine — et elle avait déjà disparu au moment de la mesure.
- ⚠⚠ Ce qui se passe alors est pire que la perte : au contrôle suivant, l'outil adopte l'état en place quel qu'il soit, et le déclare comme référence. Une éventuelle modification devient donc la nouvelle normale, et le contrôle d'après l'annonce saine. Un contrôle qui ne détecte plus rien est pire qu'aucun contrôle : il rassure.
- Trois défauts voisins mesurés au passage : une empreinte est enregistrée sans que rien ne la relise jamais ; l'outil de vérification ne se vérifie pas lui-même ; et aucun contrôle automatique ne couvre ce mécanisme.
Avertissements
- ⚠ Rien n'est corrigé : le point est ouvert, avec ce qu'il faut pour le fermer et les pièges à éviter en le faisant. Le remède demande de choisir un emplacement durable, et surtout de ne pas récupérer l'ancienne référence — rien ne dirait qu'elle décrit un état sain.
4.19.21 — 19 août 2026
Le document de sécurité interne a été remesuré et corrigé : il décrivait par endroits un état qui n'était plus le sien.
Erreurs
- Quatorze écarts confirmés par la mesure, dans un document de sécurité qui n'est lu par aucun contrôle automatique et dérivait donc librement depuis qu'il a été écrit.
- Des listes et des comptes recopiés à la main. Ils étaient justes le jour où on les a écrits, et faux la semaine suivante. Ils ne sont plus recopiés : chaque endroit indique désormais la commande qui donne la valeur du jour.
- Un serveur retiré fin juillet y était encore décrit comme en service, à trois endroits, alors que le document savait ailleurs qu'il était arrêté.
- Une protection y était annoncée pour des composants qu'elle ne couvre pas. Le texte laissait entendre plus large que la réalité ; il dit maintenant exactement ce qui est couvert, et ce qui ne l'est pas.
- ⚠ Deux reproches y étaient devenus injustes. Le code avait été corrigé le 16 août et le document était resté au constat d'avant. Un reproche périmé coûte autant qu'un défaut caché : il fait chercher là où le travail est déjà fait, et il use la confiance accordée aux autres avertissements.
- Un chapitre entier décrivait une méthode de distribution abandonnée, et se contredisait lui-même quinze lignes plus bas.
Avertissements
- ⚠ Ce document a grossi de cent cinquante lignes au lieu de maigrir : à chaque correction, ce qui était affirmé à tort est conservé et daté, plutôt qu'effacé. C'est le choix du projet — on doit pouvoir relire ce qu'on a cru.
- ⚠ Il reste hors du site : il n'est servi par aucune page et n'entre pas dans l'archive distribuée. C'est ce qui permet d'y écrire franchement.
4.19.20 — 19 août 2026
La table des alertes ne regroupait pas les alertes répétées, contrairement à ce que son code affirmait depuis le premier jour.
Erreurs
- ⚠⚠ Chaque alerte répétée créait une ligne de plus, au lieu de faire monter un compteur. La règle d'unicité de la table portait sur la date d'acquittement, qui reste vide tant que l'alerte est ouverte — et deux valeurs vides ne sont jamais considérées comme identiques par la base. La règle ne s'appliquait donc jamais dans le seul cas où elle devait s'appliquer.
- Constaté par deux alertes identiques créées à vingt secondes d'écart, toutes deux ouvertes, chacune annonçant une seule occurrence.
- ⚠⚠ Et ce n'était pas qu'un compteur faux. Le mécanisme qui signale une erreur technique le fait dès la première fois, en s'appuyant explicitement sur ce regroupement pour ne pas devenir du bruit. Deux de ses déclencheurs mettent le message d'erreur dans l'identifiant de l'alerte : une base momentanément indisponible, avec des vérifications qui repassent toutes les cinq secondes, remplissait la table.
- Quatre commentaires du code décrivaient ce regroupement comme acquis. Ils décrivaient une intention, jamais un comportement. Les quatre sont rectifiés : ils disent maintenant ce qui se passait vraiment, et depuis quand.
Modifications
- La règle d'unicité porte désormais sur une colonne calculée par la base, qui vaut l'identifiant de la cause tant que l'alerte est ouverte et rien une fois qu'elle est rangée. Une seule alerte ouverte par cause, le compteur monte ; et l'historique des alertes rangées reste sans limite.
- Rien d'autre n'a changé. La date d'acquittement garde son sens : les requêtes du panel, du bot et des outils fonctionnent sans une ligne de changement. C'est la raison d'avoir écarté les autres solutions envisagées.
- Les installations existantes sont reprises : les doublons déjà présents sont repliés — comptes additionnés, première et dernière occurrence conservées, courriel déjà envoyé mémorisé — et non supprimés. L'historique déjà rangé n'est pas touché.
Créations
- Un contrôle automatique qui lève deux fois la même alerte et exige une seule ligne à deux occurrences, vérifie qu'un défaut qui revient lève bien une nouvelle alerte, et rejoue la reprise sur une base volontairement encombrée de doublons. ⚠ Il dérive le schéma et les requêtes de reprise du code lui-même : un contrôle qui porterait sa propre copie éprouverait sa copie.
- ⚠ Quatre garanties cassées exprès avant de le retenir. Deux des essais n'avaient en fait rien modifié — le contrôle semblait aveugle alors que c'était l'essai qui n'avait rien changé. Une mise à l'épreuve se vérifie avant d'être crue.
Avertissements
- ⚠ Le seuil « signaler au troisième échec » n'était pas touché : il compte ailleurs, en mémoire. Vérifié avant d'y toucher.
4.19.19 — 19 août 2026
Les journaux d'IriX ont enfin un plafond de taille — la troisième et dernière piste ouverte par la panne du 18 août.
Modifications
- Un journal qui dépasse 16 Mo est archivé sur-le-champ, sans attendre l'échéance du calendrier. Jusqu'ici la rotation ne connaissait que le jour : celle du 18 août ayant eu lieu à 9 h 43, la panne de 11 h 26 a pu écrire 1,5 Go qui n'ont été archivés que le lendemain.
- Le chiffre vient de mesures, pas d'une estimation : les quatorze archives conservées donnent une journée saine entre 39 Ko et 456 Ko, et le journal s'écrit à environ 1 Mo par jour. Les deux journées de panne pèsent 1,12 et 1,56 Go. 16 Mo se pose au large entre les deux — trente-cinq fois le pire jour normal, un centième d'une journée de panne.
- Le plafond s'applique même quand l'archivage est réglé sur « désactivé » : ce réglage veut dire « je ne veux pas d'archive quotidienne », jamais « laissez le disque se remplir ».
- Le journal de console est désormais couvert lui aussi. Il ne l'était nulle part — ni ici, ni dans la rotation du système. Il est vide aujourd'hui, mais il ne le resterait pas si l'on relançait le bot sans passer par le système.
- La veille extérieure signale à son tour un journal qui enfle au-delà de quatre fois ce plafond : à ce stade, ce n'est plus un journal gros, c'est la rotation qui ne tourne plus.
Erreurs
- ⚠⚠ Un écrêtage ne doit PAS compter comme l'archive de la période, et il allait le faire. La rotation note la date de son dernier passage ; si un écrêtage l'avait notée, l'archive du jour aurait été considérée comme faite et sautée. En réglage mensuel, un écrêtage le 3 mars aurait annulé l'archive de tout le mois. Corrigé avant d'être livré : l'écrêtage archive, mais ne déclare rien.
- Deux rotations dans la même seconde s'écrasaient l'une l'autre. Le nom de l'archive descend à la seconde, et la copie écrase sans rien dire : la première archive était perdue en silence, sans erreur, tout au vert. Le cas était improbable avec une seule rotation par jour ; le plafond le rendait atteignable. La seconde est maintenant avancée jusqu'à trouver un nom libre. ⚠ Pas un suffixe : la purge ne reconnaît qu'une forme de nom précise, et une archive qu'elle ne reconnaît pas ne serait jamais supprimée — on aurait échangé une perte de données contre un disque qui se remplit.
Créations
- Un banc pour la rotation, qui n'en avait aucun depuis sa mise en service en juillet — alors qu'elle tronque des fichiers et en supprime. Il éprouve les six garanties sur un bac à sable, jamais sur les vrais journaux.
- ⚠ Les quatre garanties ont été cassées exprès pour vérifier qu'il les voit. L'une des mutations n'avait en fait pas été appliquée : le banc semblait aveugle alors que c'était l'essai qui n'avait rien changé. Une mutation se vérifie avant d'être crue.
Avertissements
- ⚠ Le module est chargé au démarrage du bot : le plafond n'agira qu'au prochain redémarrage.
4.19.18 — 19 août 2026
Une veille extérieure surveille désormais IriX — écrite, éprouvée, mais pas encore mise en service.
Créations
- Une sonde qui vit HORS du bot. Toute la surveillance d'IriX était portée par ses propres minuteurs : quand ils manquent, elle se tait précisément au moment où l'on en a besoin. C'est ce qui a rendu les pannes du 18 août muettes. Cette sonde est déclenchée par le système, pas par le bot.
- Elle ne regarde que ce qu'un bot malade ne peut pas simuler : la présence du refus de minuteur dans le journal, la mémoire du processus, et un journal qui a cessé d'écrire. Les quatre indicateurs qui ont menti pendant les pannes — l'état du service, l'état du lien en base, la taille du journal et le port en écoute — sont écartés, et le fichier dit pourquoi.
- Elle sait relancer le bot quand c'est grave, au plus une fois par heure : sans ce frein, une panne qu'une relance ne guérit pas produirait une boucle de redémarrages, et le limiteur d'envoi étoufferait les alertes en silence.
- Les deux unités système correspondantes sont fournies, avec leur procédure d'installation.
Avertissements
- ⚠ Rien n'est activé. La sonde et ses unités sont dans le dépôt, éprouvées sur les quatre cas qu'elles doivent reconnaître, mais leur mise en service est un geste d'exploitation qui reste à faire.
- ⚠ Sans le droit de relancer le service, la sonde alerte quand même : elle constate et écrit, elle ne peut simplement pas guérir.
Erreurs
- Le regroupement des alertes en base ne fonctionne pas. Deux alertes de même cause créent deux lignes au lieu d'incrémenter un compteur : la clé d'unicité contient une colonne vide, et une valeur vide n'est jamais égale à une autre. Le nombre d'occurrences affiché vaut donc toujours un. ⚠ Le seuil « alerte au troisième échec » n'est pas touché : il compte ailleurs. Constaté en éprouvant la sonde, noté pour être traité à part.
4.19.17 — 19 août 2026
Critique
La panne qui a privé le réseau de tous ses services le 18 août avait une tout autre cause que celle qu'on lui prêtait — et elle allait se reproduire le jour même.
Erreurs
- ⚠⚠ La panne s'est produite TROIS fois, pas une. Les archives de journal en gardent trois épisodes distincts : un avant le 18 au matin, celui qui a été constaté de 11 h 26 à 16 h 40, et un troisième de 17 h 58 à 21 h 28 — que personne n'avait vu. Le troisième a commencé une heure et quart après le redémarrage censé être le remède, et il ne s'est arrêté que parce que la machine s'est éteinte.
- Le gel de l'environnement, longtemps accusé, n'y était pour rien. Le bot numérote ses minuteurs avec un compteur qui ne repart jamais de zéro, et ce numéro ne peut pas dépasser sept chiffres. Au dix-millionième, plus aucun minuteur ne peut être posé. Un gel retarde les minuteurs ; il n'en fabrique pas. Il fallait en avoir créé dix millions.
- Et c'est bien ce qui arrivait, en une heure et demie. Le balayage des identifications posait deux minuteurs à chaque tour au lieu d'un, et n'en retirait aucun : sa population doublait toutes les soixante secondes.
- Deux fautes se cachaient l'une l'autre : 1. la procédure croyait retirer le minuteur précédent, mais elle le cherchait dans la mauvaise liste — le bot en tient deux, une par unité de temps. Le retrait échouait sans bruit ; 2. le cycle replanifiait à la fin, alors que son propre corps l'avait déjà fait sur chacun de ses quatre chemins.
- ⚠⚠ Le commentaire du code affirmait le contraire, et il datait de la veille. Il disait « le double réarmement est sans danger, la procédure tue le minuteur précédent ». C'était faux ligne par ligne. Il a été remplacé par le récit de ce qui s'est réellement passé.
- La même faute de liste existait à dix endroits : aucune des procédures d'arrêt du bot n'arrêtait quoi que ce soit. Les dix sont corrigées.
Créations
- Un contrôle automatique garde les deux règles, et il les dérive du code au lieu de réciter une liste : tout minuteur doit être retiré de la liste où il a été posé, et un cycle ne doit replanifier qu'une fois.
- ⚠ Il a d'abord été faux, et c'est son propre garde-fou qui l'a dit. Sa première version ne lisait aucun cycle — elle affirmait pourtant que tout allait bien. Sans le contrôle « des cycles ont bien été lus », elle aurait accompagné la panne suivante en silence.
- ⚠ Les deux règles ont été cassées exprès avant d'être retenues.
Avertissements
- ⚠ Ce qui a rendu la panne muette n'est pas corrigé par cette version : l'alerte par courriel passe elle-même par un minuteur, donc elle se tait précisément quand les minuteurs manquent. Une surveillance extérieure au bot reste à poser.
4.19.16 — 19 août 2026
Le titre de l'écran de connexion à l'administration est plus grand, et il s'adapte désormais à toutes les largeurs d'écran sans laisser de trou.
Modifications
- Le titre « Administration » grossit d'environ moitié : de 26 à 38 pixels sur un écran d'ordinateur, et de 21 à 28 pixels sur le plus étroit des téléphones. Le sous-titre suit dans les mêmes proportions.
- Sa taille suit maintenant la largeur de l'écran en continu, au lieu de sauter d'un coup en dessous d'une largeur donnée. Il n'y a donc plus de format d'écran à prévoir : les futurs sont couverts d'avance.
- Le sous-titre voit son interlettrage légèrement resserré, ce qui lui rend la place que le grossissement lui prend.
Créations
- Un contrôle automatique garde désormais ce réglage. Il ne cherche pas la nouvelle écriture — il dérive l'ordre des règles de mise en forme et vérifie que la dernière qui s'applique au titre est bien celle qui suit l'écran en continu. Remettre un ancien palier par-dessus le ferait rougir, là où un contrôle plus naïf serait resté vert.
- Il garde aussi le repli destiné aux personnes qui demandent moins d'animations : ce repli doit ouvrir le titre en plus d'arrêter son mouvement, faute de quoi le titre resterait invisible pour elles.
- ⚠ Les deux contrôles ont été cassés exprès avant d'être retenus, pour vérifier qu'ils savent rougir. Un contrôle qui ne peut pas échouer ne garde rien.
Erreurs
- L'ancienne mise en forme laissait une catégorie entière de téléphones de côté. Elle ne changeait de taille qu'en dessous de 400 pixels de large ; les grands téléphones récents, de 401 à 430 pixels, recevaient donc la taille prévue pour un écran d'ordinateur, dans une carte bien plus étroite. Personne ne l'avait vu parce que rien ne débordait — c'était simplement mal proportionné.
- ⚠ Une note interne affirmait que les écrans de 390 pixels étaient touchés eux aussi. Mesure faite dans un vrai navigateur : c'est faux, ils recevaient bien la petite taille. La note a été corrigée. Le trou existait, mais pas là où on le croyait.
Avertissements
- ⚠ Le palier de mise en forme qui existait auparavant a été retiré, pas seulement laissé de côté : écrit après les nouvelles règles, il les aurait annulées sur les écrans étroits. Le remettre « par sécurité » rouvrirait exactement le défaut que cette version ferme.
4.19.15 — 17 août 2026
Critique
La panne silencieuse de la veille s'est reproduite le soir même — et elle est maintenant corrigée à la racine.
Erreurs
- ⚠⚠ Le réseau a de nouveau perdu tous ses services, deux heures après la première fois. Même enchaînement : le lien tombe, le bot se reconnecte en s'inventant un identifiant de serveur, le serveur refuse. Personne n'est prévenu. La panne a été trouvée en ouvrant la séance, pas par une alerte.
- La cause était plus grave que ce qu'on en savait. Le bot n'ignorait pas seulement l'identifiant inscrit dans sa configuration : il l'effaçait et écrivait le sien à la place. Voilà pourquoi rétablir la bonne valeur ne tenait pas — elle était réécrite à la tentative suivante.
- Trois défauts distincts se tenaient par la main, et les trois sont corrigés : 1. l'identifiant de la configuration est désormais transmis lors d'une reconnexion, au lieu d'être recalculé ; 2. tout échec de connexion libère les verrous posés pendant la tentative. Sans cela le bot se croyait connecté sur un lien mort et ne réessayait plus jamais — seul un redémarrage en sortait ; 3. un refus du serveur lève une alerte. L'ancienne ne couvrait que le cas où la connexion ne s'ouvrait pas ; ici elle s'ouvrait très bien, et c'est le serveur qui disait non.
- Le journal disait aussi trop peu. Il annonçait un identifiant attribué sans dire lequel il remplaçait. La ligne avait l'air d'une opération normale.
Erreurs — le site
- ⚠⚠ L'identification IRC depuis le site ne fonctionnait plus depuis un mois. Le visiteur qui demandait à s'identifier voyait « Demande transmise » indéfiniment, quoi que fasse le robot derrière. La page interrogeait une adresse qui lui répondait une page d'erreur au lieu de la réponse attendue, et le navigateur avalait l'échec sans rien dire.
- Le même défaut privait de sa redirection celui dont l'accès à un salon venait de s'ouvrir. L'accès s'ouvrait bel et bien ; le visiteur n'était simplement jamais conduit à son salon. Cette adresse-là n'avait jamais fonctionné depuis le jour où elle a été écrite.
- La cause est la même pour les deux : les deux adresses étaient déclarées trop bas dans le fichier d'entrée du site, après le contrôle qui répond « page inconnue » et s'arrête. Elles n'étaient donc jamais atteintes. Aucune erreur, aucune trace : une page bien formée répondait à la place.
- Réparé et vérifié en ligne. ⚠ Un contrôle automatique garde désormais cette règle. Il cherche dans le fichier toutes les adresses de ce genre et exige que chacune soit atteignable — y compris celles que les pages réclament. Le panel avait déjà ce contrôle ; le site ne l'avait pas.
Créations
- Un banc de vérification dédié à la reconnexion du lien de services. Il ne récite aucune liste : il cherche dans le code tous les endroits qui ouvrent une connexion et vérifie qu'aucun n'oublie l'identifiant. C'est exactement l'oubli qui a causé la panne, et une liste écrite à la main l'aurait laissé passer une fois de plus.
Évolutions
- Le banc a été mis à l'épreuve en cassant volontairement, une par une, les neuf choses qu'il prétend garder. Il en a d'abord manqué deux : il se contentait de retrouver des mots dans le code au lieu de l'exécuter. Corrigé, puis re-éprouvé. ⚠ Une limite est écrite noir sur blanc dans le banc : il ne remplace pas un essai contre une vraie base de données, et il le dit.
- Un essai a été mené sur le réseau réel : le serveur a été arrêté une fois, et le lien de services est remonté seul en douze secondes, avec le bon identifiant et sans redémarrer le bot. ⚠ Cet essai ne prouve pourtant pas tout, et c'est écrit au registre : le serveur s'est fermé d'une manière qui n'emprunte pas le chemin réparé. La panne réelle, elle, en emprunte un autre. Le point reste ouvert tant que ce chemin-là n'aura pas été vu à l'œuvre.
4.19.14 — 17 août 2026
Le nom du réseau se frappe à la machine sur toutes les pages — et une panne silencieuse a été trouvée en production.
Évolutions
- L'écran de connexion à l'administration porte un titre. « Administration » en grand, « Réservé aux administrateurs » en dessous — au-dessus du logo, car c'est ce qu'on doit lire en arrivant, avant même de savoir de quel réseau il s'agit. Le titre se dévoile de gauche à droite, la seconde ligne après la première. ⚠ Ce n'est pas la frappe à la machine de l'accueil, et c'est volontaire : une phrase de cette longueur aurait exigé une police à largeur fixe et minuscule pour tenir dans la carte.
- L'en-tête écrit le nom lettre à lettre, sur chaque page. La frappe y est plus rapide qu'à l'accueil et sans attente préalable : c'est ce qu'on lit en premier en arrivant, et une seconde d'animation à chaque clic deviendrait une gêne. ⚠ La place du nom est réservée dès le départ — sans cela, le menu sauterait vers la droite pendant toute l'animation.
Erreurs
- ⚠⚠ Le réseau est resté sans aucun service pendant une heure quarante-cinq, sans qu'une seule alerte parte. Après une coupure du lien, le bot s'est attribué un identifiant de serveur différent de celui que sa configuration impose ; le serveur a refusé, poliment et correctement, et le bot a réessayé indéfiniment avec le même mauvais identifiant. Rien ne cassait, rien ne rougissait : les cinq services étaient simplement absents pendant que tout le reste paraissait normal.
- Corriger la valeur n'a pas suffi : un verrou interne était resté posé et empêchait toute nouvelle tentative — le même défaut que celui corrigé le jour même sur le cycle des sujets. Il a fallu redémarrer le bot.
- Réparé le soir même, réseau complet rétabli et vérifié. ⚠ Le défaut, lui, reste ouvert : n'importe quelle coupure future y ramène. C'est le point le plus urgent du registre.
4.19.13 — 17 août 2026
Le plan du chantier des éditions décrivait un état vieux de six jours.
Le document qui pilote ce chantier affirmait sept choses fausses — dont, trois fois, qu'un réglage n'existait pas alors qu'il est en place depuis le 14 août, et une étape présentée comme bloquante alors que sa condition était remplie. Chaque écart est corrigé sur place et signalé par un encart daté.
Erreurs
- Une fonction donnée comme point de départ d'une étape n'existe nulle part dans le projet. C'est l'écart le plus grave, et d'une autre nature que les autres : une date périmée se corrige en mesurant, une fonction fantôme fait bâtir sur du vide. L'étape concernée — filtrer les adresses et les secrets d'un rapport avant de l'envoyer à quelqu'un — paraissait donc largement livrée, alors que sa pièce maîtresse reste entièrement à écrire.
- Le document se contredisait lui-même : une question déclarée tranchée à un endroit était donnée comme ouverte à un autre, et bloquait une étape.
- Une liste de blocages entièrement périmée est conservée, barrée, plutôt que supprimée : chacune de ses lignes était vraie le 11 août, aucune ne l'est plus, et elle montre à quoi ressemble une condition qu'on n'a pas remesurée.
Évolutions
- ⚠ La leçon vaut plus que les corrections : un plan qui se trompe sur son propre état ne se suit pas, il s'invente. Le registre des chantiers s'applique déjà la règle de remesurer chaque point au moment où l'on y travaille ; ce document ne se l'appliquait pas, et six jours ont suffi.
4.19.12 — 17 août 2026
Le logo grandit, et le nom du réseau s'écrit à la machine.
Demandé après avoir vu la première version : un dessin plus grand, et le nom frappé lettre à lettre. Le dessin passe de 74 à 104 pixels ; le nom apparaît caractère par caractère, curseur en tête, une fois le dessin achevé.
Évolutions
- ⚠ Le nom passe en écriture à chasse fixe, et ce n'est pas décoratif : la frappe avance d'un cran par caractère, et un cran vaut la largeur d'un caractère. Dans une écriture proportionnelle, les crans se décalent des lettres et la dernière apparaît trop tôt ou reste coupée. La chasse fixe fait coïncider le cran et la lettre — et une machine à écrire l'est, de toute façon.
- Le nombre de lettres est calculé sur le nom du réseau, jamais écrit en dur : il se déréglerait au premier changement de nom.
Avertissements
- ⚠ Qui a réglé son appareil sur « moins d'animations » voit le nom entier et sans curseur — un trait qui clignote sans fin est exactement ce que ce réglage demande d'éviter.
- ⚠ Sur écran étroit, le nom réduit sa taille plutôt que de déborder.
Contrôles
- Les quatre éditions s'exécutent enfin, dans les trois langages. Le contrôle du mécanisme d'édition n'éprouvait les cas limites que dans un seul des trois, et sur la seule édition installée. Il les parcourt maintenant toutes — de 19 à 43 vérifications.
4.19.11 — 17 août 2026
Plus aucun cycle du bot ne peut s'arrêter sans le dire.
Les six cycles restants sont traités, et un contrôle neuf empêche le défaut de revenir : il dérive du code la liste des mécanismes qui se replanifient eux-mêmes, et exige que leur replanification soit hors d'atteinte de ce qui pourrait échouer.
Bugs corrigés
- Le cycle des sujets de salon ne pouvait pas être réparé sans redémarrer le bot. Il pose un verrou le temps de son passage ; une erreur en cours de route laissait ce verrou en place, et plus rien ne pouvait le relancer — ni lui-même, ni son planificateur. Verrou et replanification sont désormais rendus dans tous les cas.
- Cinq autres cycles protégés : la révocation des identifications abandonnées, le rattrapage des sujets, la purge du journal de trafic, l'évaluation des modèles de modération, et la publication automatique.
- ⚠ La publication automatique demandait un traitement différent, et le correctif habituel y aurait créé un défaut visible : elle ne doit volontairement PAS se replanifier sur un salon quitté ou dont la publication a été éteinte. Seul le travail est protégé, pas les conditions d'arrêt. Une exception : si le réglage lui-même devient illisible, on réessaie au tour suivant plutôt que de s'arrêter pour toujours.
Contrôles
- Un contrôle neuf recense les cycles et vérifie leur filet. ⚠ Sa première version n'en voyait que deux sur treize : la plupart ne posent pas leur minuteur eux-mêmes, ils passent par un planificateur. La correspondance est maintenant dérivée du code, donc elle ne se périmera pas.
- ⚠ Deux contrôles existants ont rougi au passage, et ils avaient raison : scinder une procédure déplace ce qu'ils cherchent. Tous deux vérifient désormais la chaîne entière plutôt qu'un seul maillon.
4.19.10 — 17 août 2026
Une protection contre les suppressions ne pouvait pas s'exécuter.
Le mécanisme qui supprime les pseudonymes réservés inactifs épargne les opérateurs du réseau. Avant de supprimer quoi que ce soit, il demande la liste des opérateurs — et il devait s'abstenir totalement s'il ne parvenait pas à la lire. C'est écrit dans le code depuis le début : « ne pas savoir n'est pas une autorisation ».
Or la vérification ne pouvait pas se déclencher. Quand la liste était illisible, le mécanisme répondait « je sais, et il n'y a aucun opérateur » — au moment précis où il ne savait rien. Il se serait donc cru autorisé à supprimer.
Bugs corrigés
- La protection est rétablie, et sur deux fronts : la table doit exister, et une liste vide est désormais traitée comme un aveu d'ignorance plutôt que comme un fait. Un réseau sans aucun opérateur déclaré n'existe pas en pratique ; une base qui vient de tomber, elle, répond exactement cela. Le doute va du côté qui ne supprime rien.
- Trois vérifications « la table existe-t-elle ? » faisaient l'inverse de leur but. Écrites pour rendre un mécanisme silencieux quand une table manque, elles ne s'activaient jamais — et le traitement qui suivait, lui, déclenchait une alerte critique toutes les trente secondes. Sur une installation neuve, ces gardes produisaient précisément le bruit qu'elles devaient éviter.
Contrôles
- Le contrôle de la protection éprouvait un mannequin. Il remplaçait la procédure par une version simplifiée, vérifiait ce que le balayage fait d'une réponse « je ne sais pas », et jamais la capacité du code réel à la produire — c'est-à-dire exactement l'endroit du défaut. La vraie procédure est maintenant éprouvée sur ses trois cas.
- Un contrôle faisait échouer une lecture d'une manière que la vraie lecture ne produit jamais : il validait un comportement inexistant. Corrigé.
Les boucles qui pouvaient s'arrêter sans le dire
Le bot fonctionne par cycles qui se replanifient eux-mêmes. Si la replanification échoue et que personne ne le signale, le cycle s'arrête définitivement : rien ne casse, tout se fige, et le panel continue d'afficher le dernier état connu comme s'il était frais. C'est le pire mode de panne de ce projet.
- Le cycle principal du bot est protégé. Il porte à lui seul les boutons du panel, les files de salons et de modération, la rotation des journaux et le rattrapage quotidien. Une erreur n'importe où dans son déroulement pouvait l'arrêter pour de bon ; le réarmement est désormais hors d'atteinte.
- La modération automatique aussi. Son cycle de cinq secondes n'était protégé nulle part. À l'arrêt, les décisions auraient continué d'être calculées, écrites et affichées — sans qu'aucune ne soit plus appliquée.
- Et l'observation du botnet, sur ses deux chemins restants : l'un promettait au journal que « l'observation reprendra seule », l'autre annonçait un démarrage réussi sans l'avoir vérifié.
Avertissements
- ⚠ Six cycles de moindre portée restent à traiter (nouveau point 36), dont un qui deviendrait irréparable sans redémarrer le bot. Ils sont recensés un par un, avec ce que chacun coûterait, et le remède est désormais éprouvé quatre fois.
- ⚠ Ces correctifs ne prennent effet qu'au prochain démarrage du bot.
4.19.9 — 17 août 2026
Le logo du site s'écrit à l'ouverture de la page d'accueil.
Un logo dessiné pour le réseau — une bulle de conversation qui contient trois serveurs liés — se trace en un peu moins d'une seconde en tête de l'accueil. L'ordre raconte quelque chose : la bulle d'abord, puis les liens qui se tracent, puis les serveurs qui se posent, puis le nom.
Créations
- Le logo animé de la page d'accueil, dessiné en SVG et animé en CSS — aucun script, aucune image à télécharger.
Évolutions
- ⚠ Il n'y a pas d'écran de chargement, et c'est un choix. L'idée initiale était un voile avec une barre allant de 0 à 100 % avant d'ouvrir la page. Une telle barre n'aurait rien mesuré : la page d'accueil s'affiche en quelques centaines de millisecondes, il n'y a rien à attendre. Elle aurait fabriqué le délai qu'elle prétend couvrir, et affiché une progression inventée — ce que ce projet refuse partout ailleurs. Le titre, les boutons et le bandeau d'état restent donc présents et cliquables dès la première image ; l'animation les accompagne au lieu de les masquer.
Avertissements
- ⚠ Qui a réglé son appareil sur « moins d'animations » voit le logo achevé, immédiatement : on retire le mouvement, jamais le contenu.
- ⚠ Sur écran étroit, le dessin passe au-dessus du nom — côte à côte, il lui volait la largeur et le nom du réseau se coupait.
4.19.8 — 17 août 2026
Trois points du registre fermés, et les contrôles qui les gardent en ont trouvé trois de plus.
Bugs corrigés
- Les encarts du panel se rendaient sans mise en forme — dont le bandeau d'alerte critique, affiché sur toutes les pages. Le panel et le site public emploient deux vocabulaires de style entièrement distincts, et le panel écrivait celui du site : l'avertissement le plus grave s'affichait comme un paragraphe ordinaire. Vingt et une écritures reprises dans huit fichiers.
- Une grille de trois colonnes n'en avait aucune. La règle correspondante n'existait pas dans la feuille du panel ; le bloc se rendait sur une seule colonne. Trouvé par le contrôle neuf, pas par l'œil.
- Un encart d'avertissement s'affichait sans sa couleur : il demandait une variante que la feuille ne connaît pas. Un frein d'utilisation avait donc l'apparence d'une information banale.
- Un lien du tableau de bord ne menait nulle part. Il annonçait une mise à jour disponible et rechargeait la page, sans un mot. Le panel désignait déjà la bonne destination ailleurs — il n'y avait qu'une divergence à supprimer.
- Un prêt de robot suspendu n'arrivait jamais à son terme. Le balayage horaire ne regardait que les prêts actifs : un prêt suspendu passé son échéance restait indéfiniment ni actif ni fini, sans une ligne au journal. Et lorsque la suspension venait d'une décision d'opérateur, le robot restait dans le salon.
Contrôles
- Le contrôle des styles distingue enfin les deux feuilles. Il les fusionnait avant de chercher, et déclarait donc disponible dans le panel tout ce qui n'existait que pour le site. Il lit aussi désormais les deux fichiers communs à toutes les pages, les attributs partiellement calculés, et les compositions — un mot connu accolé à un autre mot connu ne signifie pas que la combinaison existe.
- Les liens internes des pages sont vérifiés. Aucun contrôle ne les lisait : ils étaient croisés entre le menu, la table des routes et les fichiers, trois sources qui se tenaient entre elles pendant qu'un lien écrit en dur pouvait viser le vide.
- Le contrôle du balayage des prêts éprouve la vraie requête. Il la remplaçait par un mannequin, si bien que tout ce qui l'entoure était vérifié et elle-même jamais — c'est précisément là que le défaut vivait.
L'alerte, éprouvée pour de vrai
- Une panne a été provoquée en production, avec accord, pour vérifier que l'alerte fonctionne de bout en bout — et elle fonctionne : trois échecs d'archivage à trente secondes d'intervalle, l'alerte au troisième exactement, la ligne au journal, l'enregistrement, le bandeau du panel, et le courriel reçu quatre minutes plus tard. Tout a été remis en place ensuite, et vérifié : aucun journal ni archive perdu.
- Une alarme qui ne pouvait pas sonner a été retirée. Elle surveillait une lecture qui ne signale jamais d'erreur : la reprise n'avait donc aucune chance de s'exécuter. L'avertissement existe déjà, plus près de la cause. ⚠ Du code qui prétend protéger est pire que pas de code : il fait croire le cas couvert.
- Un contrôle neuf empêche ce cas de revenir, et il s'éprouve lui-même sur des exemples fabriqués — un détecteur qui ne détecte rien a exactement l'allure d'un projet sain.
Avertissements
- ⚠⚠ La file d'installation des robots n'a aucun producteur (nouveau point 35). Le service qui pose les robots est en marche depuis le 9 août et tourne à vide : rien, dans tout le projet, ne sait déposer une demande. La moitié qui consomme avait été livrée et déclarée close sans que la moitié qui produit soit branchée. C'est une décision avant d'être du code — le parc doit-il croître tout seul ?
- ⚠ Le prêt suspendu reste à constater sur un cas réel arrivé à échéance.
- ⚠ Un piège du langage, rencontré trois fois dans la même séance : la balise de fermeture de PHP écrite dans un commentaire ferme quand même le fichier. Chaque fois en citant un exemple de code dans une explication.
4.19.7 — 17 août 2026
L'adresse des visiteurs du chat était fausse là où les services la lisent.
Le serveur reçoit bien l'adresse réelle d'un visiteur du chat, et il l'affiche correctement. Mais ce qu'il annonce aux services, dans le champ prévu pour l'adresse, restait celle du relais interne : 127.0.0.1. L'historique des connexions enregistrait donc une adresse fausse pour tout visiteur du chat, depuis toujours — 26 lignes concernées, et aucune adresse publique n'y avait jamais figuré.
Une donnée fausse ne casse rien de visible ; elle fait écrire des conclusions fausses. Celle-ci avait fait croire que tous les visiteurs du chat partageaient un même compteur de connexions, et ce diagnostic-là a coûté du temps.
Bugs corrigés
- Le champ d'adresse annoncé aux services porte l'adresse réelle. L'adresse relayée était posée dans les champs textuels du client, jamais dans la structure réseau figée à la connexion — et c'est cette structure que lisait la fonction d'encodage. Elle lit désormais d'abord l'adresse que le serveur tient déjà pour vraie partout ailleurs : masques de bannissement, autorisations d'accès, plafond de connexions par adresse.
- Un bloc de commentaire de
nickprotect.havait perdu son préfixe, hérité d'une édition de fin de journée.
Modifications
Servers/overtchat-hybrid/src/s_serv.c— la fonction d'encodage de l'adresse.Servers/overtchat-hybrid/tests/test-forward.py— la campagne monte désormais un lien de services et lit ce qui y passe (7 constats → 12).
Mise en service
- Le serveur a été recompilé et remis en service le 17/08 à 10:03, avec l'accord de l'exploitant : les connexions en cours ont été coupées le temps du redémarrage, et les cinq robots se sont reconnectés dans la seconde.
- Constat pris à 10:04:22, par une connexion empruntant le chemin exact du chat. L'adresse enregistrée est celle annoncée, et non plus celle du relais : c'est la première adresse non locale que cette colonne ait jamais portée.
- Effet de bord constaté, non prévu : le reflet des personnes en ligne avait lui aussi ses adresses vides — sur la totalité de ses lignes. Il les porte depuis le redémarrage. Deux mécanismes se réparent d'un seul correctif, ce qui veut dire que le second n'était éprouvé par personne.
Avertissements
- ⚠ Les lignes déjà enregistrées restent fausses : c'est un historique, il n'est pas réécrit. Elles se lisent avec la date du correctif en tête.
- ⚠⚠ Une campagne d'essai du serveur était rouge, et son échec n'atteignait personne (point 34 du registre, ouvert et fermé le même jour). Trouvée en vérifiant l'absence de régression : 13 contrôles en échec sur 22, et déjà avant ce correctif — mesuré en recompilant la version précédente. Elle n'était simplement jamais lancée. Le réseau en service n'a jamais été concerné.
Le lanceur de bancs
- Les campagnes du serveur tournent désormais à chaque passage. Elles étaient rangées parmi les essais « qui exigent un serveur en face » — inexact : elles démarrent le leur, et n'ont besoin que du programme compilé. Ce classement les laissait hors de tout contrôle ordinaire. Elles ont maintenant leur propre rubrique, et là où le programme n'est pas compilé, elles sont nommées avec la raison au lieu d'être comptées en échec.
- Le banc du lien de services dit enfin ce qui lui manque. Il exécute le vrai code des services dans un interpréteur qui ne connaît pas les commandes du bot ; quand l'une manque, il la nomme désormais en un constat, au lieu de rendre treize échecs sans coupable.
- Le décompte passe de 81 à 82 essais au vert, la campagne du serveur comprise — 17 sur 17, sans échec.
- ⚠ Les sept contrôles existants de cette campagne restaient au vert sur le code fautif. Ils ne lisaient que ce que le serveur répond au visiteur, jamais ce qu'il annonce aux services : c'était l'angle mort exact. La correction a été éprouvée en l'annulant volontairement, pour vérifier que les nouveaux contrôles la rattrapent.
4.19.6 — 16 août 2026
Le registre des chantiers disait l'inverse de ce qu'il annonce.
Ce document s'ouvre sur « ce qui n'est pas fait ». Mesuré : sur ses 4 092 lignes, 72 % décrivent ce qui est fait, et 24,7 % ce qui reste — près de trois lignes de passé pour une ligne de travail ouvert.
Erreurs
- Son préambule était entièrement périmé. Il annonçait un point comme ouvert alors que le même fichier le déclare fermé, une section vide alors qu'elle ne l'était pas, et qu'un chantier ne devait pas s'ouvrir alors qu'il l'est sur décision. Le journal des revues successives a été retiré au profit d'un tri qui se dérive des titres.
- Deux points ouverts portaient un titre faux — l'un annonçait « rien n'est écrit » alors que la moitié du travail a été livrée le jour même, l'autre décrivait l'état du matin. Tous deux sont maintenant lisibles pour ce qu'ils sont.
- Quatre points fermés sont physiquement rangés sous « défauts ouverts ». Le document applique sa propre règle de rangement de façon inégale : c'est signalé en tête, avec la consigne de se fier au marqueur du titre et non à la section.
Avertissements
- Le tri complet des 33 points figure désormais en tête : 22 constatés fermés, un corrigé sans preuve, six réalisables par du code, cinq qui demandent un geste ou une décision de l'exploitant. ⚠ Il est daté et doit se remesurer, pas se recopier.
4.19.5 — 16 août 2026
La protection des pseudonymes ne pouvait pas s'armer ailleurs qu'ici.
Le serveur sait réserver un pseudonyme par mot de passe : quiconque le demande sans le mot de passe se le voit refuser, dès la poignée de main. Deux défauts empêchaient ce mécanisme de fonctionner sur une installation neuve.
Bugs corrigés
- Le fichier des pseudonymes protégés était déposé avec des droits que le serveur refuse. Il contient des empreintes de mots de passe, et le serveur refuse — délibérément — de charger un fichier lisible au-delà de son propriétaire. L'installateur le posait pourtant en lecture de groupe : la protection ne s'armait donc jamais, et le seul signe était une ligne d'erreur au démarrage. Invisible ici, où le fichier avait été corrigé à la main ; invisible ailleurs au début, le fichier livré étant vide.
- Huit endroits documentaient un format d'empreinte incomplet — dont le fichier livré et ses deux exemples, c'est-à-dire le gabarit même qu'on recopie. Une entrée écrite à la main d'après cette documentation est acceptée, mais avec une seule passe de calcul au lieu de cent mille : une protection cent mille fois moins coûteuse à casser, qui a toutes les apparences du bon fonctionnement. Un seul fichier du dépôt avait raison depuis le début.
Créations
- Trois contrôles ajoutés à
Bootstrap/tests/test-conf-modele.sh, tous dérivés : le serveur contrôle-t-il les droits, l'installateur pose-t-il le bon mode, et aucun document n'enseigne-t-il le format court. Éprouvés en les cassant.
Erreurs
- Le registre affirmait qu'un mot de passe qui ne correspond pas laisse le robot basculer « sans une ligne au journal ». Le serveur journalise bien le refus — c'est le robot qui reste muet. Regarder au mauvais endroit revenait à ne pas regarder.
- Un document du projet écrivait qu'un service écoutant sur
0.0.0.0l'est « IPv6 comprise ». C'est faux : cette écriture ne couvre que l'IPv4.
Avertissements
- Appliquer la protection d'un seul côté ne casse rien sur le moment, et c'est ce qui la rend dangereuse : la vérification n'a lieu qu'à la prise du pseudonyme, jamais sur une session déjà ouverte. Le défaut dormirait jusqu'au redémarrage suivant, c'est-à-dire le lendemain matin.
- La pose des mots de passe elle-même reste un geste humain. En revanche tout ce qui suit — écrire, recharger, reconnecter, vérifier — ne l'est pas, et la vérification est justement l'étape qu'on oublie.
4.19.4 — 16 août 2026
Le chat d'une installation neuve ne refusera plus ses propres visiteurs.
Sur ce serveur, le chat du site sait depuis le 3 août de quel visiteur vient chaque connexion. Le réglage qui le permet avait été posé ici seulement : le modèle de configuration livré aux autres installations ne l'a jamais reçu.
Chez elles, toutes les personnes présentes sur le chat étaient donc comptées comme une seule : au-delà de cinq en même temps, la suivante était refusée alors qu'elle était seule chez elle — et un bannissement visait tout le monde ou personne. C'est le même oubli que la liste des villes, plus tôt aujourd'hui : une correction appliquée à ce serveur, jamais à ce qui est distribué.
Modifications
Servers/overtchat-hybrid/conf/overtchat.conf.example— le réglage, et à côté ce qui le rend sûr : le port du chat n'écoute que la machine elle-même. Ce n'est pas la liste qui protège, c'est cela.
Créations
Bootstrap/tests/test-conf-modele.sh— six contrôles sur le modèle livré. Le principal ne demande pas « telle ligne est-elle là » mais « un serveur qui accepte des connexions de navigateur doit dire à qui il fait confiance » : l'exigence vaut aussi pour les réglages à venir. Éprouvé en le cassant deux fois.
Erreurs
- Le registre des chantiers affirmait ce défaut pour ce serveur-ci, où il était faux depuis douze jours, et le présentait comme un arbitrage à rendre. Il n'y avait rien à arbitrer : la correction manquait ailleurs. Le registre est repris, mesure par mesure.
Avertissements
- Le mensonge venait d'une colonne de la base. Pour un visiteur du chat, le serveur enregistre la bonne adresse dans un champ et
127.0.0.1dans un autre. Qui mesure depuis la base conclut que tout le monde partage une adresse. 26 lignes portent ce désaccord ; le point est ouvert au registre. - Le chat échappe entièrement au limiteur de rafales, et cela ne se corrige pas au même endroit : ce limiteur agit avant que l'adresse du visiteur soit connue. C'est permissif, non restrictif.
- Aucun refus pour cause de plafond n'a jamais eu lieu sur ce serveur, cinq journaux vérifiés. Le chat n'a pas encore connu deux visiteurs distincts en même temps.
4.19.3 — 16 août 2026
Le panel dit quand le contrôle de ville est éteint.
La version précédente a fait partir la liste des communes dans l'archive d'installation. Restait le plus gênant : rien nulle part ne signalait son absence. Le module portait pourtant en commentaire que « le panel l'affiche, et la validation le journalise » — les deux moitiés étaient fausses, et la fonction concernée n'avait aucun appelant. Le contrôle pouvait donc être éteint sans que personne ne puisse l'apprendre.
Ce qui change à l'écran
- La page Chat du panneau d'administration affiche un avertissement quand la liste des villes manque, ou quand elle est trop courte pour être crédible, avec la commande qui la reconstitue.
- Rien n'est dit au visiteur du chat : lui annoncer que la vérification est levée reviendrait à l'annoncer à qui voudrait s'en servir.
Modifications
Web/pages/chat.php— l'encart, et le pourquoi de son coût : lire l'état demande d'indexer les 123 906 communes, mesuré à 145 ms. Un contrôle « allégé » écrit à côté aurait créé une seconde vérité sur la même règle, qui aurait divergé au premier changement de seuil.Web/inc/asv.php— la promesse fausse est remplacée par ce qui est vrai, et par les quatre raisons mesurées de ne pas brancher le journal.Web/tests/test-asv.php— deux contrôles nouveaux, dérivés : une page du panneau doit lire cet état, et aucune page publique ne doit le révéler. Les deux ont été éprouvés en les cassant volontairement.
Avertissements
Deux défauts ont été constatés en chemin et inscrits au registre des chantiers, sans être corrigés ici :
- Dix-huit encarts du panneau se rendent sans mise en forme, dont le bandeau d'alerte critique présent sur toutes ses pages : ils emploient une classe qui n'existe que dans la feuille de style du site public. Le contrôle censé l'attraper examine les deux feuilles réunies, et ne peut donc pas le voir.
- Le lien vers la mise à jour, au tableau de bord, ne mène nulle part : la page se recharge sans rien dire.
4.19.2 — 16 août 2026
Le contrôle de ville du chat fonctionne enfin ailleurs qu'ici.
Sur ce site, indiquer sa ville au chat passe par une liste de 123 906 communes : une ville mal orthographiée est refusée, et celle qui est publiée au /WHOIS est le nom de la liste, jamais ce qui a été tapé. Ailleurs, ce contrôle n'existait pas. Le fichier n'était emporté par aucune archive d'installation : sur toute installation tierce, le chat acceptait donc n'importe quel texte à la place d'une ville, et le publiait tel quel.
Rien ne change pour les visiteurs de ce site — le contrôle y a toujours fonctionné. Le changement est pour ceux qui installent le logiciel chez eux.
Modifications
Web/inc/distrib.php— la liste des villes part désormais dans l'archive du site. Elle est entrée dans la liste qui sert à la fois à composer l'archive et à décider de la refabriquer : un fichier ajouté à la seule archive aurait été emporté mais jamais rafraîchi, et les installations suivantes auraient reçu une liste figée sans que rien ne le signale.Web/public/index.php— un commentaire affirmait que la ville publiée vient toujours de la liste ; la ligne qu'il annonçait disait le contraire lorsque la liste manque. Il dit maintenant ce que le code fait.
Bugs corrigés
- Le contrôle de ville était éteint en silence sur toute installation tierce. Sans la liste, la vérification ne se fait pas — c'est un choix assumé, une liste absente étant une panne d'exploitation et non une fraude — et le texte saisi part au
realname. Ce mode dégradé, prévu comme l'exception, était l'état normal partout ailleurs qu'ici. - Le banc qui devait l'attraper était aveugle.
Web/tests/test-distrib.phpporte un contrôle nommé « tout ce que le site lit hors deWeb/part dans l'archive », et il était vert. Il ne reconnaissait qu'une façon d'écrire un chemin, quand le module du chat en emploie une autre. Les deux sont désormais reconnues, et le contrôle a été éprouvé en le cassant volontairement : il rougit maintenant, là où il restait vert.
Avertissements
- L'archive du site passe de 1,22 à 1,79 Mio (+46 %). Aucune limite de taille ni de délai n'est approchée.
- Réinstaller remplace la liste. Un exploitant qui aurait reconstitué la sienne avec d'autres pays la verrait écrasée au prochain passage par le menu d'installation du site. La protéger empêcherait aussi toute mise à jour de la liste : le point est ouvert au registre des chantiers, il demande un arbitrage.
4.19.1 — 16 août 2026
Le chat s'ouvre dans son propre onglet, et refuse d'en ouvrir deux.
Jusqu'ici, le webchat vivait dans la page : cliquer « Charte » pour vérifier une règle coupait la conversation, sans le moindre avertissement.
Ce qui change à l'écran
- Le lien Chat, dans le menu comme dans le pied de page, ouvre un onglet à part. Naviguer sur le site ne coupe plus la connexion.
- Une seconde connexion depuis un autre onglet est refusée, avec un message qui dit quoi faire plutôt que d'annoncer une panne.
Modifications
Web/inc/site.php— le menu et le pied passent par la même fonction pour ce lien : deux écritures, une seule vérité.Web/public/assets/chat.js— le verrou d'unicité.
Avertissements
- ⚠⚠ Le verrou expire, et c'est ce qui compte. Un onglet fermé brutalement ne prévient personne. Un drapeau posé une fois survivrait à qui l'a posé et lui interdirait de revenir définitivement : on pose donc un signe de vie, renouvelé toutes les 5 secondes et périmé au bout de 15.
- ⚠ Ce n'est pas une garde, c'est un confort. Le stockage du navigateur est cloisonné par profil : deux navigateurs, une fenêtre privée ou deux machines ne se voient pas. Cela évite le double-clic et l'onglet oublié, pas un abus voulu. Le seul plafond réel est celui du serveur.
- ⚠ Un lien, et non une fenêtre ouverte par script : le moment où le chat sait quoi ouvrir est postérieur à la vérification d'âge, donc hors du geste de l'utilisateur — où tout navigateur bloque une ouverture de fenêtre.
4.19.0 — 16 août 2026
Le chat vous avertit quand on vous cite.
Jusqu'ici, rien ne le faisait : un message où votre pseudo apparaissait ressemblait à tous les autres, et un salon que vous ne regardiez pas affichait le même compteur qu'il s'agisse de bavardage ou d'une question qui vous était posée.
Ce qui change à l'écran
- La ligne qui vous cite porte un fond et un trait dans la marge.
- Le salon où l'on vous cite, quand vous êtes ailleurs, bat doucement dans la colonne, et sa pastille passe de la couleur d'activité à celle d'alerte.
- Ouvrir le salon éteint tout.
Créations
Web/tests/test-chat-mention.php— 34 constats, qui exécutent la fonction avecnode. La réécrire en PHP éprouverait la copie, pas ce qui tourne chez le visiteur.
Modifications
Web/public/assets/chat.js—meCite()détecte aux frontières de mot et sans la casse. Les deux comptent : IRC ne distingue pas Paul de paul, et sans frontières un pseudo court sonnerait sur « salut », « alors », « canal ». On apprendrait en une soirée à ignorer le signal.Web/public/assets/site.css— la classe de citation est la seule ligne du fil qui porte un fond : les huit autres types ne jouent que sur la couleur du texte, et les cinq teintes disponibles y sont déjà toutes prises. L'animation respecteprefers-reduced-motion.
Bugs corrigés
- ⚠ La comparaison « ce message vient-il de moi » se faisait avec la casse. Si le serveur renvoie votre pseudo dans une casse différente de celle de la connexion, votre propre message s'affichait comme venant de quelqu'un d'autre — et, avec la nouveauté du jour, vous vous seriez averti vous-même.
Avertissements
- ⚠⚠ Aucune expression régulière n'est construite avec le pseudonyme, et c'est le point de conception principal. Un pseudo IRC peut contenir des crochets, une barre verticale, des accolades, un accent circonflexe, une barre oblique inverse ou un accent grave — tous spéciaux dans une expression régulière. Un motif mal échappé ne lèverait aucune erreur : il cesserait simplement de mordre, et la personne ne serait plus jamais avertie sans que rien ne le signale.
- ⚠ Trois cas ne déclenchent volontairement aucune alerte : vos propres messages, les services (leurs réponses citent systématiquement le destinataire — sonner à chaque identification reviendrait à ne plus sonner), et le rejeu de l'historique à la reconnexion.
- ⚠ Le banc n'éprouve pas où la détection est branchée : cela demande un navigateur et deux clients. Le point 27 de
Markdown/CHANTIERS.mdreste ouvert jusqu'à ce constat.
4.18 8 versions · 16 août 2026 7 créations 3 évolutions 15 modifications 12 bugs corrigés 2 erreurs 8 avertissements
4.18.7 — 16 août 2026
Le seul contrôle d'intégrité du projet refusait des archives saines pendant cinq minutes après chaque déploiement.
L'installateur télécharge l'archive du site, puis demande au site l'empreinte publiée, et refuse d'installer si les deux diffèrent. C'est le seul endroit du projet où une empreinte est réellement vérifiée, et c'est un bon garde-fou.
Mais l'archive est refabriquée dès qu'une page change, tandis que le manifeste des empreintes était mis en cache cinq minutes sur le seul critère de l'âge. Après chaque déploiement s'ouvrait donc une fenêtre pendant laquelle l'installateur recevait la nouvelle archive et l'ancienne empreinte — et abandonnait sur « Empreinte incorrecte — archive refusée ».
⚠ Constaté en provoquant la panne, pas en relisant le code : un fichier du site touché sans que son contenu change, puis le geste exact de l'installateur. Divergence immédiate, fenêtre encore ouverte à +120 s, refermée à +180 s.
C'est un contrôle qui criait au loup — précisément ce que le point 24 du registre reproche à une alerte qui se lève à tort : on apprend à l'ignorer.
Bugs corrigés
Web/inc/distrib.php— le cache du manifeste compare désormais les dates des fichiers servis, et non le seul âge du cache. L'économie reste entière : une cinquantaine defilemtimeau lieu d'autant dehash_file.
Modifications
Web/tests/test-distrib.php— un constat qui vérifie que chaque empreinte publiée correspond au fichier réellement servi. Il ne mesure pas le cache mais sa conséquence : un cache réparé autrement le satisfera tout autant. Éprouvé par mutation — l'ancien comportement rétabli, il rougit et désigne nommémentsite.
Erreurs
- ⚠ Le docblock du manifeste renvoyait à
safe_encrypt verifyau présent, fonction supprimée le 29/07/2026. C'est vraisemblablement cette phrase qui a engendré celle de la page de téléchargement, laquelle promet encore que « chaque composant est vérifié avant d'être posé » — ce qui reste faux. Le docblock dit maintenant ce que le manifeste garantit réellement, et renvoie au point 26 du registre. - ⚠ La ligne « Version courante » de ce document était restée à 4.18.5 pour un dépôt en 4.18.6, le jour même où elle se décrit comme ayant déjà menti.
4.18.6 — 16 août 2026
Six outils étaient installés chez vous sans pouvoir être mis à jour.
L'archive du site emporte le répertoire Tools en entier : tout ce qui s'y trouve atterrit donc sur le disque de la personne qui installe. La veille de l'installateur surveille ensuite les fichiers .sh et .tcl, et demande au site chaque fichier qu'elle surveille. Six d'entre eux n'étaient pas dans l'inventaire de téléchargement : ils répondaient 404 en permanence, ce qui donne un rapport de mise à jour en rouge et — l'alerte par courriel étant branchée — un message toutes les six heures sur des fichiers parfaitement sains.
Mesuré en interrogeant les vraies adresses du site, pas en relisant le code.
⚠ C'est la même régression que le 31/07/2026, sur dix fichiers à l'époque. Le commentaire du module la racontait au passé alors qu'elle s'était refermée. Tools/edition.sh, ajouté le matin même, en faisait partie.
Et en creusant, un défaut plus grave que les six : le prêt de robot ne pouvait pas s'installer du tout.
L'installateur ne se contente pas de recevoir l'archive : il télécharge aussi les sources déclarées dans sa configuration. _overop_sync_source() lit git.overop.files[].repo et demande chaque chemin au site ; _overop_deploy() l'appelle avant toute pose. Les deux chemins d'OverOp répondaient 404.
⚠⚠ Cela ne pouvait pas se voir ici. En développement, le dépôt porte les fichiers, OVEROP_SOURCE_DIR les trouve sur le disque, et la synchronisation n'a jamais besoin de sortir. Le prêt de robot fonctionnait parfaitement — et n'aurait fonctionné chez personne d'autre.
Bugs corrigés
Tools/bancs.sh,desinstaller.sh,edition.sh,faire-archive-hybrid.sh,villes-importer.shettests/test-desinstaller.shsont désormais servis.Robots/overop/overop.tcletoverop.conf.exempleaussi — sans quoi le prêt de robot était ininstallable hors de cette machine.
Modifications
Web/inc/distrib.php— les six entrées manquantes. ⚠ La liste reste explicite : distribuer un script demeure une décision fichier par fichier, et un script déposé dansTools/ne devient pas public du seul fait d'y être.Web/tests/test-distrib.php— deux constats croisés. Le premier dérive du disque la liste des scripts emportés par l'archive ; le second dérive du script d'installation les sources qu'il va télécharger. Chacun exige que la cible figure à l'inventaire réel. Éprouvé par mutation — quatre mutations, quatre détectées, dont les deux qui comptent : un script neuf déposé dansTools/, et une source neuve déclarée dans l'installateur.- ⚠ Ces constats interrogent l'inventaire réel, pas le texte du module. La première version cherchait les chemins dans le source et accusait 27 fichiers parfaitement servis : les modules d'IriX y sont distribués par un motif générique, donc leur chemin n'y figure nulle part en toutes lettres.
Avertissements
- ⚠ Un fichier ne peut pas être à la fois installé et introuvable. Pour écarter un script de la distribution, il faut aussi l'écarter de l'archive : les deux décisions vont ensemble, et c'est ce que le nouveau constat impose.
4.18.5 — 16 août 2026
Le chat du site était inaccessible à tout le monde depuis la 4.18.0. Un nom de champ, et un seul.
En allant simplement constater l'ASV dans un navigateur — ce que le point 22 du registre Markdown/CHANTIERS.md réclamait —, le formulaire s'est rempli normalement, puis a répondu « La vérification n'a pas abouti. Réessayez dans un moment. » Quelles que soient les valeurs saisies. Aucune connexion au réseau n'était même tentée.
La cause : le navigateur envoyait son jeton anti-CSRF sous le nom csrf, quand le serveur le lit sous _csrf — le souligné manquait. Le jeton partait, juste et à jour, sous un nom que personne ne lisait. Le contrôle répondait donc 400 à chaque tentative, en texte brut là où du JSON était attendu, ce qui transformait un refus précis en un message vague qui ne disait pas que le chat était mort.
Les trois autres appels du même fichier employaient déjà le bon nom : c'est l'ASV, livré en 4.18.0, qui a introduit le quatrième.
Bugs corrigés
- Le webchat refusait l'entrée à tout visiteur (
Web/public/assets/chat.js). Constaté en service sur le site public, puis reconstaté corrigé.
Modifications
Web/public/assets/chat.js— le jeton part sous le nom que le serveur lit.Web/tests/test-asv.php— trois constats qui croisent les deux côtés : le nom attendu est dérivé decsrf_check()dansWeb/inc/auth.php, les noms employés sont relevés danschat.js, et ils doivent coïncider. Aucun des deux n'est écrit en dur dans le banc.
Avertissements
- ⚠ Cinq constats de ce banc étaient au vert pendant toute la panne. Ils vérifient que la route contient les bons appels — pas qu'elle réponde. Un banc qui lit du texte source ne remplace pas une page ouverte dans un navigateur.
- ⚠ Le compte de « douze points » de la 4.18.4 ci-dessous est faux : il y en a treize. Le branchement de
IriX/cmds/irix-chanq.tcl(file-illisible), livré par le même commit, ne figure dans aucune des énumérations.
4.18.4 — 16 août 2026
L'alerte passe de 4 à 12 points branchés — et deux endroits où elle ne DOIT pas crier.
Le point 24 du registre disait : « le mécanisme marche, il n'est presque pas branché ». Un recensement des catch du bot avait rendu une trentaine de candidats ; huit ont été retenus.
Ce qui parle désormais
| Module | Ce qui échouait en silence | Niveau |
|---|---|---|
uline | dialecte non implémenté — le lien ne montera jamais | erreur |
uline | plus aucun SID libre — aucun nouveau lien ne peut monter | erreur |
uline | la socket refuse la connexion | echec |
uline | lien fantôme — monté, et le serveur ne nous voit pas | echec |
botreg | trois installations ratées : le service d'installation est arrêté | erreur |
mandat | le contrôle des prêts tourne à vide (rien n'est observé) | erreur |
logrot | la rotation échoue en boucle, les journaux grossissent | echec |
ia | le moteur de modération tourne à vide, la file grossit | echec |
Sept modules appellent maintenant l'alerte, contre deux.
⚠⚠ Le choix entre echec et erreur n'est pas cosmétique
alerte::echec a une fenêtre d'une heure et un seuil de trois : il est INERTE sur ce qui tourne une fois par heure, le compteur repartant de zéro entre deux passages. Le contrôle des prêts (scan::hourly) et le service d'installation passent donc par erreur.
⚠ Et botreg a une seconde raison : le comptage des trois échecs existe déjà (echecs_consecutifs). Passer par alerte::echec aurait exigé trois refus de plus — neuf échecs avant d'alerter, pour un service à l'arrêt depuis le troisième.
⚠⚠ Deux endroits où l'alerte ne doit PAS se lever
Ces deux exceptions valent plus que dix branchements : une alerte qui se lève au mauvais moment apprend à ignorer le bandeau, et l'on perd alors toutes les autres.
- Une table absente ne lève rien. Au premier démarrage, les tables n'existent pas encore :
chanq::scan,logrot::scanet d'autres lisent avantdb_initet le documentent comme le cas normal. Sans cette exception, toute installation neuve lèverait une volée d'alertes critiques dès sa première minute. - L'alerte n'est PAS branchée sur
set_error, l'entonnoir des échecs de socket — parce qu'il porte aussi « Socket désactivé », c'est-à-dire le refus légitime de connecter un lien volontairement coupé. Elle est branchée sur les cas, ce qui donne en prime des messages précis.
Le banc garde les deux.
Modifications
IriX/tests/test-alerte.tcl— 39 vérifications, dont un constat qui compte les modules appelants : un module d'alerte avec un seul point d'appel ne ferme pas un trou « à la jointure », il en crée un. 3 mutations, 3 attrapées.
Avertissements
⚠⚠ CINQUIÈME OCCURRENCE DU MÊME PIÈGE DE BANC dans la séance. Le constat « l'alerte n'est pas branchée sur set_error » s'écrivait set_error.*?alerte:: — et cherchait donc jusqu'au prochain alerte:: n'importe où dans les 12 000 lignes suivantes. Il rougissait sur du code juste. Le motif est désormais borné au corps de la procédure.
Un motif qui ne dit pas où il s'arrête ne mesure pas ce qu'on croit.
⚠ La contre-épreuve des mutations a d'abord échoué sur un bac à sable incomplet — Tools/ n'y était pas copié, et le banc y cherche alerte-courriel.php. Même piège que test-doublons le 14/08.
⚠ Rien n'est constaté pour ces huit branchements. Aucun n'a encore levé d'alerte réelle : ils sont écrits, éprouvés au banc, et jamais vus agir. Le point 24 reste ouvert.
4.18.3 — 16 août 2026
Une tentative de lien qui n'aboutit pas se voit enfin — et l'alerte critique est CONSTATÉE de bout en bout.
⚠⚠ Le défaut, trouvé en faisant le geste
SerVuS a demandé : « rehash irix et tente toi-même de faire Lier et contrôle les retours ». Trois « lier » sur OverOp-H ont donné :
12:55:39 attempt OverOp-H
12:57:41 attempt OverOp-H
12:58:41 attempt OverOp-H
état = unlinked · echecs = 0 · AUCUNE alerte
On pouvait cliquer indéfiniment sans que rien ne le signale.
La cause était écrite dans le code et jugée close : « le résultat de link NE PROUVE RIEN — Eggdrop rend 1 quand il a LANCÉ la tentative, pas quand le lien est monté ». C'était juste, et incomplet : personne ne regardait ensuite si elle avait abouti. La poignée de main échoue de façon asynchrone, echecs reste à zéro, et la garde des « trois échecs de suite » ne se déclenche donc jamais.
⚠ Ni un banc ni une relecture ne pouvaient le montrer : le code fait exactement ce qu'il dit. Seul le geste réel le révèle.
Bugs corrigés
traiter_actionsnote désormais ce qu'il attend ;observerconclut au cycle suivant — c'est le seul endroit qui puisse le savoir.- ⚠ On ne conclut rien avant un cycle complet : juger trop tôt ferait déclarer en échec un lien qui monte encore. Même prudence que la péremption de l'observation.
- ⚠ Une réussite remet le compteur à zéro : sinon trois échecs espacés de réussites finiraient par barrer un pair qui marche, et
action_verdictrefuserait « 3 échecs de suite » sur un lien sain. - ⚠
alerte::echecet nonerreur: une tentative isolée peut rater pour une raison passagère. C'est sa répétition qui dit « ce lien ne montera jamais ».
✅✅ Le chemin complet, constaté
13:03:14 attempt OverOp-H
13:04:14 error echecs=1 ← le silence est levé
13:06:14 error echecs=2
13:09:14 error echecs=3 → ALERTE CRITIQUE, dans les DEUX journaux
ligne #1 en base · bandeau présent sur le tableau de bord
13:12:16 courriel envoyé à service.overtchat@free.fr
13:13:53 rangée — la ligne reste, le bandeau DISPARAÎT
Le point 23 du registre est clos, et le 24 s'ouvre : le mécanisme marche, il n'est branché que sur quatre points.
Avertissements
⚠ VERSION a été montée sans que ce journal soit écrit, et c'est test-update.php qui l'a attrapé — quatre constats rouges. Le défaut est déjà nommé dans l'historique du dépôt (« le commit précédent l'annonçait, encore ») ; il vient d'être refait. Monter VERSION et écrire sa section sont un seul geste, pas deux.
⚠ Deux pièges mesurés pour la suite du branchement, et ils sont dans le point 24 : les catch autour d'un db_exec ont une branche morte, et alerte::echec est inerte sur ce qui tourne une fois par heure.
4.18.2 — 16 août 2026
L'alerte devient VISIBLE : bandeau, page, acquittement, courriel.
La 4.18.1 avait livré le module seul — il écrivait dans les deux journaux et en base, et aucune page ne le montrait. C'était le point 23 du registre, et il portait sa propre condamnation : une alerte que personne ne voit est exactement le défaut qu'elle combat.
Créations
Web/inc/alerte.php— lecture, compte par niveau, acquittement, pastille de menu, bandeau.Web/pages/alertes.php— les ouvertes, les rangées, et ce que la page ne peut pas dire.Tools/alerte-courriel.php— l'envoi, sur le montage deTools/alerte-pseudo.php.::soc::alerte::poster_courrielset son cycle, dansirix-alerte.tcl.
Modifications
Web/inc/layout.php— le bandeau sur toutes les pages, l'entrée de menu avec sa pastille.Web/public/admin.php— l'actionalerte_acquitter, au niveau op.Web/inc/routes.php,Web/tests/test-routes.php— la route et son talon.IriX/tests/test-alerte.tcl— 34 vérifications.
Évolutions
⚠⚠ LE COURRIEL NE PART PAS DE lever, et c'est la décision qui compte. exec php est synchrone et ouvre une connexion SMTP ; lever peut être appelée depuis n'importe quelle boucle. Un envoi placé là bloquerait la boucle d'événements d'Eggdrop le temps du SMTP — le bot cesserait de répondre au réseau pour annoncer qu'il a un problème. Un balayage borné (3 par passage, toutes les 5 min) poste ce qui attend, comme nickexpire.
⚠ mail_at est posé par le SCRIPT, pas par le bot après coup : entre l'envoi et la pose, un plantage ferait renvoyer le même courriel à chaque passage — et une alerte qui se répète toutes les minutes dans une boîte est le plus sûr moyen d'apprendre à filtrer ce réseau.
⚠ Seules les crit font un bandeau, et seules les crit partent par courriel. Les warn vivent sur leur page, signalées par la pastille du menu. Un bandeau qui s'affiche pour un avertissement finit par se contourner du regard, et le jour où il porte une vraie panne, personne ne le lit.
⚠ Ranger n'efface pas : acquit_at est posé, la ligne reste avec son compte et ses dates. La clé UNIQUE portant sur (cle, acquit_at), un défaut qui revient lève une nouvelle alerte au lieu de rouvrir l'ancienne en silence.
⚠ La page dit ce qu'elle ne peut pas dire. Un encart explique qu'une page vide ne signifie pas que tout va bien : le bot ne signale que ce que le code défaillant lui dit. Sans cet encart, cette page deviendrait la nouvelle section 🔴 vide du registre — rassurante et fausse.
Bugs corrigés
Tools/alerte-courriel.phplisait$r['ok']alors quesmtp_send()rend une liste[ok, message]. Tout envoi réussi aurait été compté comme un échec, et le courriel serait reparti à chaque passage. Vérifié dansmailer.php:104plutôt que supposé.- Un caractère chinois (
非) s'était glissé dans le texte du bandeau, à la place de « non ».
Avertissements — trois pièges de banc dans la même séance
⚠⚠ Le motif plus large que la chose, QUATRE fois aujourd'hui. Trois fois le mot cherché existait dans un commentaire (csrf_check, asv_ages, bind time — ce dernier dans la note qui explique pourquoi on n'en emploie pas) ; la quatrième, exec se trouvait au milieu de db_exec ". Chercher un mot, c'est aussi le trouver dans ce qui en parle — et au milieu d'un identifiant.
⚠⚠ Un piège propre à Tcl, à connaître : la gourmandise de toute l'expression est fixée par son premier quantificateur. Un [^\n]* gourmand en tête rend le (.*?) suivant gourmand aussi — l'extraction du corps d'une procédure filait jusqu'au bout du fichier et emportait les suivantes. Le constat rougissait sur du code juste.
⚠ Et j'ai d'abord imputé cela aux fins de ligne CRLF, avec une explication détaillée écrite dans le banc. Il n'y avait aucun CRLF — mesuré ensuite. L'explication fausse a été retirée. Ce n'est pas Tcl qui trompe, c'est de conclure avant de mesurer.
⚠ Un garde-fou a évité un vert-à-vide : le constat « lever ne lance aucun exec » était vert sur un corps vide. Un second garde-fou vérifie désormais que l'extraction s'arrête — un corps qui ferait la moitié du fichier serait « vrai pour une mauvaise raison ».
Ce qui reste
⚠ Non constaté : IriX doit recharger pour que le module vive, et aucune alerte réelle n'a encore été levée. Le chemin complet — un échec répété, une ligne en base, un bandeau, un courriel, un acquittement — n'a jamais été parcouru.
⚠ Un seul appelant. Le module ne couvre que le refus de botnet. Poser l'alerte n'a pas protégé du motif « à la jointure » : il en a créé un de plus, et il faudra brancher les autres mécanismes un par un.
4.18.1 — 16 août 2026
L'alerte critique — côté bot. Ce qui casse en service, et qui le dit.
Demandé par SerVuS : « mettre une alerte en cas de dysfonctionnement du code ou action… faire un log critique ».
Pourquoi ce module existe
Il est né le jour même où deux défauts parfaitement silencieux ont été trouvés à la main, à une heure d'intervalle : six actions du panel mortes (4.17.5), puis le bot refusant toute action en la prenant pour un doublon d'elle-même (4.17.6).
⚠⚠ Aucun des deux n'était une erreur. Rien n'a planté, rien n'a été journalisé en ERROR, aucun banc n'a rougi — l'un affichait un refus propre à l'écran, l'autre journalisait correctement un refused à chaque cycle. Ce qui les trahissait, c'est qu'ils se répétaient à l'identique.
Créations
IriX/cmds/irix-alerte.tcl— table<bot>_alertes,lever,echec(répétition),erreur(technique),ouvertes.IriX/tests/test-alerte.tcl— 26 vérifications, 7 mutations, 7 attrapées.
Les deux déclencheurs, et pourquoi leurs seuils diffèrent
Tranchés par SerVuS : « les deux, seuils séparés ».
| Déclencheur | Seuil | Pourquoi |
|---|---|---|
| répétition d'un échec fonctionnel | 3 en moins d'une heure | un refus isolé est souvent le bon comportement — quelqu'un demande ce qu'il n'a pas le droit d'avoir. C'est sa répétition qui dit « personne n'y arrivera jamais » |
| erreur technique | dès la première | une erreur avalée par un catch n'est jamais « normale » |
Ce qui empêche ce module de devenir du bruit
Le projet a payé l'inverse : 171 erreurs SQL journalisées depuis le 27/07, sans conséquence, qui ont fait accuser la base et les droits MySQL à chaque lecture du journal. Une alerte qui crie pour rien n'est plus lue.
- La déduplication par clé — une même cause fait UNE alerte dont le compteur monte. 171 occurrences identiques donnent une ligne à 171, pas 171 lignes. ⚠ C'est elle, et non un seuil, qui permet de lever dès la première erreur technique sans inonder.
- La clé désigne la CAUSE, jamais l'instant.
botnet:refus:OverOp-H:linket non un horodatage — qui ferait une alerte par occurrence. - La fenêtre oublie. Trois échecs espacés de trois semaines ne sont pas une répétition : le mécanisme marche, il a raté trois fois en un mois. ⚠ Une alerte fausse coûte plus cher qu'une alerte manquée — elle apprend à ignorer les vraies.
- Le compteur repart de zéro après avoir levé. Sans cela, chaque échec suivant lèverait encore, la déduplication masquerait le défaut, et le compte monterait sans que personne comprenne pourquoi.
Le premier appel réel
::soc::botnet::traiter_actions appelle alerte::echec sur un refus. Ce n'est pas un exemple : c'est exactement ce refus-là, répété à chaque cycle, qui a caché que le panel ne pouvait rien faire. Le journal le disait correctement, trois fois de suite, et personne ne lisait cette table.
Évolutions
⚠ Les DEUX journaux, toujours. ::soc::log écrit dans IriX-sql.log, putlog dans IriX.log, et ils ne portent pas les mêmes lignes. Le projet s'est déjà fait prendre en cherchant la trace d'une purge dans le mauvais fichier et en concluant qu'elle n'avait pas tourné.
⚠ Si la base refuse, l'alerte n'est pas perdue : les journaux l'ont déjà. Une alerte qui disparaîtrait parce que la base est tombée serait perdue au pire moment — la base qui tombe est le genre d'incident qu'on veut voir.
⚠ La clé UNIQUE porte sur (cle, acquit_at) : un défaut qui revient après avoir été acquitté lève une nouvelle alerte au lieu de rouvrir l'ancienne en silence. On veut voir qu'il est revenu.
⚠ Les trois jointures sont franchies dans le même commit — le source dans IriX-sql.tcl, et les deux manifestes de Bootstrap/overtchat-setup.sh. C'est ce qui a manqué à irix-edition.tcl le 14/08 : le dépôt marchait, une installation neuve serait morte au chargement. test-modules-charges.tcl le vérifie — 25 modules, trois croisements.
Bugs corrigés
- Un
putlog "module chargé"avait été écrit au niveau du fichier, juste sous le commentaire qui l'interdit. Retiré. La preuve qu'un module est chargé ne s'écrit pas dans le module — c'esttest-modules-charges.tclqui la donne.
Avertissements
⚠⚠ CE MODULE NE DÉTECTE RIEN DE LUI-MÊME. Il est appelé par le code qui sait qu'il vient d'échouer. Un mécanisme qui n'appelle pas alerte::echec restera aussi muet qu'avant — poser ce module ne protège pas du motif « à la jointure », il en crée un de plus. Un seul appelant existe à ce jour.
⚠ Rien n'est encore visible. Le bandeau du panel, la page de lecture, l'acquittement et le courriel restent à écrire : pour l'instant, une alerte va dans les journaux et dans la base, et personne ne la voit sans requête.
⚠ Non constaté : IriX tourne l'ancien code tant qu'il n'a pas rechargé.
4.18.0 — 16 août 2026
L'ASV est BRANCHÉ : formulaire, contrôle serveur, publication au choix, et la charte réécrite.
Créations
- La route
?p=asv·Web/public/index.php— POST, jeton anti-CSRF, plafond par adresse, validation, composition durealname.
Modifications
Web/site/chat.php— âge (menu 16→99), sexe (les cinq réponses), ville, et la case « Afficher mon ASV aux autres », décochée.Web/public/assets/chat.js— appelle la route AVANT d'ouvrir le WebSocket, et envoie lerealnamereçu du serveur.Web/site/charte.php— trois passages réécrits (voir plus bas).Web/tests/test-asv.php— 65 constats, dont 13 sur la route et le formulaire. 14 mutations au total, 14 attrapées.
Évolutions
⚠⚠ LE CONTRÔLE PROTÈGE LE FORMULAIRE, PAS LE PORT. Le webchat ouvre un WebSocket directement vers le serveur IRC : aucun PHP ne se trouve sur ce chemin. Le JavaScript appelle donc ?p=asv avant de connecter, et n'ouvre rien si la route refuse. Mais qui ouvre le WebSocket lui-même, ou se connecte avec mIRC, ne passe pas par là. C'est cohérent avec l'arbitrage retenu — « webchat obligatoire, IRC facultatif » — et c'est une limite connue, pas une faille cachée.
⚠ Le menu, les required et le JavaScript ne gardent RIEN : ils se contournent en trois secondes avec les outils du navigateur. Ils évitent un aller-retour. La décision est prise par la route, et nulle part ailleurs.
⚠ Le realname est composé PAR LE SERVEUR, à partir des valeurs validées — la ville est celle du fichier, jamais celle qui a été tapée. Le navigateur ne fait que le transmettre. S'il le composait, on pourrait y écrire n'importe quoi, et ce champ est lu au /WHOIS par tout le réseau.
⚠ Une panne de la vérification NE LAISSE PAS ENTRER — l'inverse du choix fait pour la liste de villes absente, et pour une raison : là, c'est une donnée d'installation qui manque ; ici, c'est le contrôle lui-même qui n'a pas répondu, et l'on ne sait donc rien de l'âge.
⚠ Seul un CONTOURNEMENT compte pour le plafond. Une ville mal orthographiée est une faute de frappe : la compter mènerait à barrer quelqu'un qui cherche sa commune, exactement l'inverse du but.
La charte — trois passages, et le premier était un mensonge
⚠⚠ Le site promettait le contraire de ce qui est mis en place. Il fallait donc réécrire, pas ajouter :
- En tête de charte, avant toute règle de conduite : le chat est réservé aux 16 ans et plus, l'âge est déclaré et le site n'a aucun moyen de le vérifier. ⚠ Un site qui promet une chose pendant que le formulaire en impose une autre rend la règle inapplicable — on peut lui opposer sa propre page.
- Section 11 : le filtre « minorité » reste malgré l'interdiction. On pourrait croire l'inverse ; c'est justement quand la barrière a été contournée que cette détection sert, et elle protège d'abord celui qui l'a contournée.
- Section 10 : l'ASV entre au tableau des données, et la publication au choix y est dite aussi clairement que sur le formulaire.
Bugs corrigés
asv_rate_block()cherchait l'adresse dansdetail;audit_log()la range dans sa colonneip. Le compte serait resté à zéro et le plafond n'aurait jamais bloqué personne — sans que rien de visible ne casse.
Avertissements
⚠⚠ DEUX MUTATIONS ONT ÉTÉ RATÉES, POUR LA MÊME RAISON, DANS LE MÊME BANC. Mes constats cherchaient csrf_check() et asv_ages() n'importe où dans le fichier — et ces textes existent aussi dans les commentaires que j'y avais écrits. Commenter l'appel en // csrf_check(); laissait le constat vert.
Chercher un mot, c'est aussi le trouver dans ce qui en parle. C'est le défaut « motif plus large que la chose », déjà recensé — et refait deux fois ici. Les deux constats sont désormais ancrés (^\s*csrf_check\(\); en début de ligne, foreach\s*\(\s*asv_ages\(\) pour la boucle).
⚠ Rien n'est constaté en service. Le formulaire a été rendu hors navigateur — 84 âges de 16 à 99, aucun 13 ni 15, les cinq sexes, case décochée — mais personne n'a encore ouvert le chat et tapé une ville. Le compte claude du panel a un mot de passe haché, et le webchat demande un vrai navigateur.
⚠ L'ASV n'est pas encore stocké pour les opérateurs : il vit le temps de la connexion, dans le realname. Le tableau de la charte le dit ainsi, et il dit vrai — mais la modération n'y a donc accès que si la personne publie.
4.17 11 versions · 15 août 2026 → 16 août 2026 13 créations 4 évolutions 14 modifications 7 bugs corrigés 6 avertissements
4.17.10 — 16 août 2026
Le socle de l'ASV — Âge, Sexe, Ville. Le module, la liste des villes, le banc. Rien n'est encore branché sur le chat.
Demandé par SerVuS : « sur le site > Chat, inclure ASV pour faire un ident au pseudo qui se connecte — connexion interdite en dessous de 16 ans, contrôler si la ville existe vraiment, et en sexe Homme/Femme/Transgenre/Non-Binaire/Secret ».
Les quatre décisions, toutes tranchées par SerVuS
| Décision | Ce qui l'a emportée |
|---|---|
| 16 ans, pas 13 | en France, l'âge du consentement autonome au traitement de données personnelles est 15 ans (loi Informatique et Libertés ; le RGPD laisse 13 à 16 aux États). En dessous, consentement parental obligatoire — impraticable ici. 16 place au-dessus avec une marge |
| publication au CHOIX, décochée | l'ASV sert la modération sans organiser la mise en relation. Le risque tient à la publication, pas à l'âge minimum : « 13F Nantes » et « 16F Nantes » sont l'une comme l'autre une annonce exploitable |
| menu 16 à 99 | une valeur en dehors ne peut venir que d'une requête forgée. C'est ce qui rend le blocage légitime — et évite de punir une déclaration honnête, que le formulaire ne permet pas de faire |
| refus + blocage court, jamais un bannissement | l'adresse est partagée (un lycée, une box) ou change seule (4G). Un bannissement ferme frapperait une classe entière et manquerait celui qui change de réseau |
Créations
Web/inc/asv.php— âges, sexes, normalisation et recherche de ville, validation, détection de contournement, composition durealname, plafond par adresse.Tools/villes-importer.sh— constitueConfig/villes.txtdepuis GeoNames.Config/villes.txt— 123 897 entrées, 1,8 Mo. France, Belgique, Suisse, Luxembourg et Québec.Web/tests/test-asv.php— 52 constats, 8 mutations, 8 attrapées.
Évolutions
⚠⚠ CE MÉCANISME N'EMPÊCHE AUCUN MINEUR D'ENTRER, et il faut le dire. Un âge déclaré n'est pas vérifiable : un enfant de douze ans choisit « 25 » et passe. Ce module pose une règle, il ne protège personne à lui seul.
Ce qui protège, c'est la modération des privés et sa catégorie minorite. ⚠ Elle devient PLUS utile, pas moins : elle est désormais le filet qui rattrape ceux qui ont menti au formulaire. Ne pas la retirer sous prétexte que les mineurs sont « interdits ».
⚠ Le chat ne dépendra jamais d'Internet. L'import est un geste d'installation joué une fois ; asv.php ne lit ensuite que le fichier local. Une API tierce en panne aurait fermé le chat, ce qui est inacceptable pour un contrôle de ville.
⚠ Une liste absente LAISSE ENTRER — seul endroit du module où le défaut prudent ouvre au lieu de fermer. Une liste manquante est une panne d'exploitation, pas une entrée frauduleuse : refuser tous les visiteurs pour un fichier absent transformerait un oubli d'installation en coupure de service. Le prix de ce choix est qu'il doit se VOIR, d'où asv_villes_etat().
⚠ Le script refuse d'écrire en dessous de 20 000 villes. Un import tronqué par une coupure réseau produirait une liste qui refuse des villes en ayant l'air de marcher — pire qu'un fichier absent, que l'on sait au moins nommer.
⚠ Licence CC BY 4.0 : les données GeoNames imposent de citer la source. L'en-tête du fichier la porte et doit y rester. Aucun conflit avec le MIT — ce sont des données, pas du code.
Bugs corrigés — tous trouvés en regardant les VILLES, jamais les comptes
⚠⚠ Bruxelles, Anvers et Genève étaient ABSENTES d'une liste de 92 613 entrées. GeoNames nomme chaque lieu dans sa langue locale ou en anglais — « Brussels », « Antwerpen », « Geneva » — et le nom français vit dans la colonne des variantes. Un Belge tapant « Bruxelles » aurait été refusé, sur un réseau francophone, par une liste qui avait l'air complète.
⚠⚠ Puis « Paris » est devenu « París ». Les variantes ajoutées, le nom affiché devenait le premier venu dans l'ordre alphabétique : « Le Havre » → « Lé Hâvre », « Anvers » → « Anvèrs ». Le fichier porte désormais un rang, et un nom principal l'emporte toujours sur une variante.
⚠⚠ Une seconde règle a été essayée puis RETIRÉE — « à rang égal, la forme accentuée gagne ». Elle corrigeait « Geneve » → « Genève » et cassait Anvers : « Anvèrs » est aussi une variante accentuée, et c'est une faute d'orthographe. Aucune heuristique locale ne distingue un accent juste d'un accent faux. On préfère un accent manquant à un accent faux — « Geneve » se lit, « Anvèrs » est une erreur que le réseau publierait au WHOIS de quelqu'un.
⚠ awk écrivait avec > au lieu de >>, et il est relancé pour chaque pays : le fichier était tronqué à chaque appel, et seul le dernier pays aurait survécu — un import « réussi » et presque vide.
⚠ unzip n'est pas installé sur la Debian de production. Le script emploie python3 et son module zipfile à défaut, et dit lequel il utilise.
Avertissements
⚠ Rien n'est branché sur le chat. Le formulaire, chat.js, le stockage et la charte restent à écrire. Le module est éprouvé, il n'est pas encore employé — c'est exactement le motif « à la jointure » que ce projet a payé cinq fois.
⚠ La charte disait alors le CONTRAIRE — ce n'est plus vrai. Remesuré le 25/08/2026 : la phrase « dire son âge n'est pas une faute » n'existe plus dans Web/site/charte.php, qui traite désormais l'âge dans sa section dédiée et cite elle-même cette contradiction. ⚠ Le renvoi portait un numéro de ligne sur un fichier vivant, et il désignait aujourd'hui un tableau de durées de conservation : nommer, jamais numéroter. Reste vrai : pseudos-interdits.example (dans Servers/overtchat-hybrid/conf/) porte que « le réseau accueille des mineurs ». Tant qu'elles ne sont pas réécrites, le site promet une chose et le formulaire en imposera une autre.
⚠ Deux mutations sur huit ont d'abord été ratées, et chacune disait autre chose. L'une était un vrai trou du banc — rien n'éprouvait la règle principal/variante, et la casser laissait tout vert. L'autre montrait que le code résiste par deux gardes indépendantes, dont chacune suffit : la mutation n'en cassait qu'une. Une mutation qui ne rougit pas ne prouve pas que le code tient.
4.17.9 — 16 août 2026
« Prêt de robot » devient un groupe de menu, un Eggdrop par ligne.
Demandé par SerVuS : « le mettre entre Anope et Overtchat et faire le même système que Anope — afficher en liste les eggdrop enregistrés ».
Même raisonnement que pour Anope le 02/08 : c'était une entrée du groupe réseau, au même rang qu'un salon. Or on n'ouvre pas « le prêt de robot », on va voir OverOp-H — et il fallait ouvrir la page pour savoir lequel regarder.
Créations
eggdrop_nav_entrees()·Web/inc/botreg.php— une vue d'ensemble en tête, puis une entrée par robot portant sa présence réelle, périmée à 300 s : un robot sans nouvelles redevient « Inconnu », jamais « Hors ligne ». Motif d'anope_nav_entrees(), repris et non réinventé.
Modifications
Web/inc/layout.php— le groupe est placé entreAnopeet le groupe réseau, d'oùrobotsa été retiré.Web/pages/robots.php— le robot désigné par?c=est mis en avant.Web/tests/test-routes.php— talon pour le nouveau groupe.Web/tests/test-botreg.php— trois constats revus ou ajoutés (voir plus bas).
Évolutions
⚠⚠ LA CLÉ DE DÉCLINAISON EST c, COMME ANOPE — et ce n'est pas un détail. nav_actif() ne compare que le paramètre c (liste blanche de layout.php). Une cible en robots&b=… n'y serait jamais discriminée : les cinq entrées s'allumeraient ensemble, et le menu montrerait cinq endroits où l'on ne peut être qu'une fois. Réutiliser c évite d'élargir cette liste, et une seule clé de déclinaison dans tout le menu vaut mieux que deux. Un banc le garde désormais.
⚠ Cache statique dans eggdrop_nav_entrees(), comme anope_clients_presence() : le menu est rendu deux fois par page — barre latérale et menu replié — et sans lui, toute page du panel paierait deux requêtes de plus.
⚠ Un robot désactivé garde son entrée, avec sa pastille propre. Le faire disparaître ferait croire qu'il n'existe plus alors qu'il est mis de côté — et l'on ne pourrait plus cliquer pour le réactiver.
⚠ Ce menu ne dit RIEN du botnet, délibérément : « lié » y désigne le lien DCC entre Eggdrop, qui vit dans une autre table et a sa propre page. Un robot peut tourner sans être lié, et c'est l'état par défaut. Mêler les deux dans une seule pastille donnerait un état que personne ne saurait relire.
⚠ Le clic mène quelque part. Sans la mise en avant, les cinq entrées mèneraient à une page rigoureusement identique — un menu qui promet cinq destinations et n'en offre qu'une est pire qu'une entrée unique, parce qu'on le croit. Un ?c= inconnu le dit (« ce robot n'est plus déclaré au parc ») plutôt que d'afficher la page sans un mot.
⚠ La comparaison est insensible à la casse — la base compare sans, PHP avec — et le nom affiché est toujours celui de la base, jamais celui reçu : un ?c= forgé ne peut rien écrire à l'écran. Constaté sur trois cas, dont overop-h et un nom inexistant.
Bugs corrigés
⚠⚠ UN BANC QUI CHERCHAIT UNE CHAÎNE LITTÉRALE, ET QUI SERAIT TOMBÉ POUR UNE RAISON FAUSSE. test-botreg.php vérifiait que layout.php contient le texte 'robots' => [. L'entrée étant devenue un groupe construit par une fonction, la chaîne a quitté layout.php sans que le menu perde quoi que ce soit — le banc aurait rougi sur un code parfaitement juste. Il dérive désormais l'entrée de la fonction qui la produit.
Un banc qui cherche du texte à l'endroit où il croit qu'il est garde la forme du code, pas ce qu'il fait. C'est la même famille que les cinq façons connues qu'un banc a de mentir.
Avertissements
⚠ Trouvé par un agent d'exploration lancé en parallèle, et je serais passé dessus : le constat textuel ci-dessus comme la contrainte sur la clé c. Les deux auraient été découverts après coup, l'un par un banc rouge inexplicable, l'autre par un menu qui s'allume en entier.
4.17.8 — 16 août 2026
Le suivi en direct d'une demande de botnet, et le cadre du tableau de bord complété.
⚠⚠ Le suivi est un CORRECTIF, pas un confort
Le matin même, le panel a monté un lien de botnet — et SerVuS a cru à une panne. Il avait cliqué, la page n'avait rien montré de vivant, et le lien montait pendant ce temps. Le panel pose l'action, IriX la consomme à son cycle : entre les deux, jusqu'à 60 secondes de silence, indistinguables d'un échec.
Une action différée sans retour visible se lit comme une action qui a échoué.
Créations
botnet_suivi()·Web/inc/botnet.php— trois états, et les confondre était le défaut :
| État | Ce qu'on affiche | Rafraîchit ? |
|---|---|---|
| en cours | « demande en cours, IriX la traite en moins de 60 s » | oui, toutes les 5 s |
| bloqué | « la demande n'est pas consommée — c'est IriX qu'il faut regarder » | non |
| en erreur | bannière rouge, motif et compte d'échecs | — |
Web/tests/test-botnet-suivi.php— 20 constats, 5 mutations, 5 attrapées.
Modifications
botnet_peers()rendaction_age. ⚠ Sans lui,last_error— qui n'est jamais effacé tant qu'une action ne réussit pas — afficherait une erreur d'il y a trois jours comme une panne du moment, et la bannière rouge deviendrait un meuble.Web/pages/dashboard.php— le cadre porte désormais le botnet, ce qui attend une décision (demandes de salon, avis de travaux, mise à jour), la version et l'édition.
Évolutions
⚠⚠ LE RAFRAÎCHISSEMENT S'ARRÊTE, et c'est le point le plus important. Au-delà de trois cycles, on cesse de recharger et l'on dit que personne ne répond. Une page qui tourne indéfiniment devant un bot mort affiche « en cours » pour l'éternité — c'est le même mensonge que l'état périmé, que cette page traite déjà en le rendant INCONNU plutôt que « pas lié ». « Je n'ai pas de nouvelles » n'est pas « c'est en cours ».
⚠ Le seuil vaut trois cycles et non un : la machine gèle avec la veille de Windows (point 15), et un seul cycle crierait au moindre retard.
⚠ Un âge inconnu rend « en cours », jamais « bloqué ». Un pair jamais traité ne dit pas depuis quand il attend : le déclarer bloqué serait une accusation sans mesure.
⚠ Le compteur de rechargements est borné et remis à zéro dès qu'il n'y a plus d'attente. Sans cette remise à zéro, la demande suivante ne se rafraîchirait pas — un correctif qui ne marche qu'une fois est pire qu'aucun, parce qu'on croit l'avoir.
⚠ Le miroir PHP ↔ Tcl est désormais gardé : le banc dérive OBS_PAS du module et le compare à BOTNET_CYCLE, et fait de même pour la péremption d'observation en la dérivant des deux fichiers. S'ils divergent, le panel promet un délai que le bot ne tient pas — et l'on croira à une panne pour la raison exacte qui a motivé ce correctif.
Bugs corrigés
- Le cadre lisait
update_cache_lire()['disponible'], qui n'existe pas (la clé estavailable). ⚠ Et surtout : une version publiée non vide n'est pas une mise à jour disponible — elle peut être égale ou antérieure à l'installée. C'est exactement l'erreur du 11/08/2026, où j'avais conclu que le panel proposait une mise à jour vers une version plus ancienne. Seulupdate_verdict()compare, et lui seul tranche. - Deux appels à des fonctions inexistantes attrapés avant tout rendu :
botreg_duree_texte()(c'estbotnet_duree()) etapp_version()(c'est la constanteAPP_VERSION). Les deux auraient tué la page.
Avertissements
⚠ Le cadre passe de 658 à 734 ms. Toutes les sources ajoutées restent sur la machine : update_verifier() — qui interroge le site — est délibérément écartée au profit du cache. Un tableau de bord qui dépend du réseau met dix secondes à s'afficher le jour où le réseau tombe, c'est-à-dire le jour où on l'ouvre.
⚠ Le rechargement automatique n'est pas constaté. Le banc garde la DÉCISION — rafraîchir ou non ; que le navigateur recharge bien au bout de cinq secondes et s'arrête à la borne ne se voit que dans un vrai navigateur.
4.17.7 — 16 août 2026
Le tableau de bord porte un cadre de vue générale, à droite.
Demandé par SerVuS : « un cadre sur la droite […] l'affichage des services actif, inactif, erreur […] un cadre d'information générale de tout ».
Créations
statut_synthese()·Web/inc/statut.php— la vue générale, dérivée destatut_tout(). Elle n'invente aucune mesure : le tableau de bord et la page Statut disent donc forcément la même chose.Web/tests/test-statut-synthese.php— 19 constats, 6 mutations, 6 attrapées.- La grille
.avec-cote·app.css— contenu + colonne latérale de 320 px, repli en une colonne sous 1100 px.
Modifications
Web/pages/dashboard.php— la page passe en deux colonnes, le cadre à droite.
Évolutions
⚠⚠ AUCUNE TROISIÈME SOURCE D'ÉTAT. Le tableau de bord lisait stack_status(), la page Statut lit statut_tout() : deux sources coexistaient déjà. En ajouter une troisième pour ce cadre aurait reproduit à l'identique la charge d'un robot, écrite à trois endroits et payée trois fois (4.16.29 → 4.16.32) — chacune juste de son côté, et la troisième trouvée seulement en RENDANT la page. La synthèse dérive donc, elle ne mesure pas.
⚠⚠ POURQUOI LA PRODUCTION ET LES ESSAIS SONT SÉPARÉS. Six composants sur neuf sont arrêtés en permanence — serveurs d'essai, Anope non installé — et c'est normal. Au même rang que la production, ils donneraient un cadre où six lignes crient tous les jours ; un cadre qui crie toujours cesse d'être lu, exactement comme une étiquette « critique » posée sur ce qui ne l'est pas. Le verdict d'ensemble ne regarde donc que la production, et le champ production qui le permet existait déjà.
⚠ Les anomalies, elles, remontent des deux familles : un port d'essai tenu par un inconnu mérite d'être vu — il ne doit simplement pas faire dire que le réseau va mal. C'est l'anomalie qu'on filtre, pas son origine.
⚠ Seuls les messages err et warn remontent. Les info sont justes et détaillés sur la page Statut ; dans un cadre de synthèse ils noieraient l'écart qu'on vient y chercher. « On garde l'écart, pas le constat. »
Avertissements
⚠ Le cadre coûte ~300 ms — statut_synthese() interroge systemctl et ss pour neuf composants, sur la page la plus ouverte du panel. Mesuré : 328 ms au premier appel, 284 au second, sans aucun cache. Le coût est assumé, pas masqué : une valeur mise en cache devient une seconde vérité, avec sa propre péremption. C'est le prix de l'OBSERVÉ contre le DÉCLARÉ.
⚠ minmax(0, 1fr) et non 1fr, dans la grille. Une piste en 1fr a pour taille minimale la largeur de son contenu le plus large : un tableau un peu long, et la page déborde horizontalement. Invisible sur grand écran, et ne se voit qu'au doigt.
⚠ Rendu hors navigateur : 27 437 octets, aucune notice, <div> équilibrés à 144/144. ⚠ La première mesure en annonçait 17 — toutes imputables à l'INSTRUMENT, qui écrivait avant d'ouvrir la session. Soupçonner l'outil de mesure avant le code.
⚠ Pas encore vu dans un vrai navigateur — le compte claude du panel a un mot de passe haché. Le rendu prouve que la page se construit ; il ne prouve pas qu'elle est belle ni lisible au doigt.
4.17.6 — 16 août 2026
🔴 LE TROU DE 4.17.5 EN CACHAIT UN SECOND : le bot REFUSAIT toute action du panel, en la prenant pour un doublon d'elle-même.
Trouvé une heure après le correctif précédent, en regardant agir — et non en relisant. SerVuS a cliqué « Lier », le panel a répondu « en attente de traitement », et la table botnet_log a dit le reste :
10:49:32 refused OverOp-H « une action est déjà programmée : link »
10:49:32 refused OverOp-T « une action est déjà programmée : link »
10:50:32 refused OverOp-H « une action est déjà programmée : link »
Le fait
::soc::botnet::traiter_actions sélectionne les lignes dont action_pending n'est pas none, puis soumettait l'enregistrement à action_verdict. Or ce verdict porte une garde anti-doublon — « une action est déjà programmée » — qui regardait ce même action_pending. L'action se refusait elle-même, à chaque cycle, indéfiniment.
⚠ Aucune action du panel n'a JAMAIS pu aboutir. Le lien monté le 15/08 à 21:30 l'avait été par .link à la main dans la console — le seul chemin qui marchait. Le chemin du panel n'avait jamais pu s'exécuter, son action étant morte au portier (4.17.5, corrigé le matin même).
⚠⚠ Deux questions étaient posées à une seule procédure : « peut-on POSER cette action ? » et « peut-on EXÉCUTER celle qui est posée ? ». La garde anti-doublon est juste pour la première et interdit tout pour la seconde.
Bugs corrigés
action_verdictprend un paramètreen_traitement(0 par défaut, donc aucun appelant existant ne change). À 1, il lève cette seule garde ; toutes les autres s'appliquent d'autant plus que l'état a pu changer entre la pose et l'exécution — pair désactivé, devenu lié, trop d'échecs.traiter_actionsl'appelle avec 1.
Modifications
IriX/tests/test-botnet.tcl— six constats ajoutés, 69 vérifications, et les deux sens tenus : en traitement l'action passe, hors traitement elle bloque toujours. Les trois autres gardes sont éprouvées en traitement, là où on pourrait croire le drapeau permissif. 3 mutations, 3 attrapées.
⚠⚠ LE BANC ÉTAIT DÉJÀ VERT SUR CE POINT, et c'est la leçon de la version. Il vérifiait « une action déjà programmée bloque » — règle juste, posée à la mauvaise question. Un banc ne rougit pas sur une règle correcte appliquée au mauvais endroit : cela ne se voit qu'en regardant agir.
⚠ Le sixième constat est celui qui compte le plus : il lit le corps réel de traiter_actions et vérifie que l'appel porte bien le drapeau. Sans lui, retirer le 1 remettrait le défaut à l'identique en laissant les cinq autres verts — « garder la porte en regardant la serrure », déjà attrapé sur test-bindtime.tcl le 14/08. La mutation M2 le confirme : elle n'est vue que par lui.
Avertissements
⚠ Déployé sans rechargement, délibérément — même motif que les 14 et 15/08 : le passage horaire de 11:00 porte le contrôle des prêts, et le retour d'IriX prend 70 à 95 s. Le code est en place ; IriX tourne encore l'ancien en mémoire jusqu'au prochain rechargement.
⚠ Ce correctif n'est donc PAS constaté, et il ne peut pas l'être avant ce rechargement. Le constat à obtenir : cliquer « Lier », puis voir dans l'activité du botnet un attempt — et non plus un refused.
4.17.5 — 16 août 2026
🔴 SIX actions du panel étaient MORTES — toute la page Botnet, et le dialecte des liens ULine.
Signalé par SerVuS depuis la page : « Administration > Botnet, la partie pour lier les eggdrop met : action inconnu ». Le défaut était bien plus large que le bouton signalé.
Le fait
Toute requête POST du panel traverse un portier, Web/public/admin.php:359 :
if (!array_key_exists($action, ACTION_LEVELS)) {
flash('Action inconnue.', 'err');
Six actions étaient traitées par un case et absentes de cette table. Le portier les rejetait donc avant que leur code ne soit atteint : ce code était mort depuis qu'il était écrit.
| Action morte | Page | Ce qu'elle ne faisait pas |
|---|---|---|
botnet | Botnet | lier / délier un pair |
botnet_add · botnet_del | Botnet | déclarer / retirer un pair |
botnet_enable · botnet_reset | Botnet | activer / remettre les échecs à zéro |
proto | ULines | déclarer le dialecte d'un lien |
⚠⚠ proto n'avait JAMAIS été signalé, et c'est ce qui rend ce défaut instructif : on ne change pas de dialecte tous les jours. Un bouton rarement cliqué peut être mort des semaines sans que personne le remarque. Ce n'est pas l'usage qui trouve ce genre de trou — c'est un croisement.
Bugs corrigés
- Les six actions sont déclarées dans
ACTION_LEVELS. Les niveaux ont été tranchés par SerVuS, jamais devinés :botnet,botnet_enableetbotnet_reseten admin — actes d'exploitation réversibles, même famille quesocket;botnet_addetbotnet_delen owner — désigner la machine à laquelle le bot parlera est de la configuration d'infrastructure, commeoper_saveetprotection_save.protoen admin, par stricte analogie avecsocket, son voisin de page et d'objet.
Créations
Web/tests/test-panel-actions.php— le croisement qui aurait attrapé ce trou, et qui attrapera le prochain. Il dérive trois listes des fichiers et n'en énumère aucune : les entrées d'ACTION_LEVELS, lescasedu switch, et ce que les pages postent réellement. Il les croise dans les trois sens. 10 constats, 6 mutations, 6 attrapées — dont la reproduction du trou d'origine et celle duprotoque personne n'avait vu.
⚠ Ce que le banc NE fait PAS : il ne lit rien de ce que fait une action, ni du niveau qui lui est donné. Une action déclarée au mauvais niveau le satisfait pleinement. Il constate qu'elle est joignable, rien de plus — comme test-bindtime.tcl et test-modules-charges.tcl, c'est un point de passage obligé, pas une preuve.
⚠ Son quatrième constat garde le portier lui-même : sans lui, les trois croisements resteraient verts en gardant une porte qui n'existe plus. C'est le défaut attrapé le 14/08 sur test-bindtime.tcl — « garder la porte en regardant la serrure » —, repris et non réappris.
Avertissements
⚠ Ce correctif n'est PAS constaté. Le code est déployé, le banc est vert, les mutations mordent — mais personne n'a encore vu un bouton de la page Botnet faire ce qu'il annonce. Le compte claude du panel a un mot de passe haché, donc inutilisable : le constat appartient à SerVuS. Tant qu'il n'a pas eu lieu, le point reste ouvert au registre, et c'est la règle.
4.17.4 — 15 août 2026
✅ Le point 2 est CLOS. Une information a traversé le lien de botnet, les deux extrémités tracées.
21:38:37 IriX .tcl ::soc::botnet::ping OverOp-T
21:38:37 OverOp-T triggering bind ::overop::on_ping
21:38:37 OverOp-T OVEROP: écho de IriX — répondu
21:38:37 IriX triggering bind ::soc::botnet::on_pong
21:38:37 journal « écho revenu en 0s — le lien porte »
Le lien était monté depuis 21:29:41 — challenge, réponse, socket ESTAB — et le module l'avait constaté seul au cycle suivant.
⚠⚠ Ce point a failli se fermer deux fois sur une preuve creuse
Un lien monté sans verbes aurait eu l'air sain et n'aurait rien porté : c'est ce constat qui a durci l'exigence de fermeture en « constater qu'une information PASSE ». Puis un transport écrit d'un seul côté a produit un silence complet — pas de réponse, pas de trace, pas d'erreur.
⚠ Un transport a deux extrémités ; un banc qui n'en regarde qu'une ne garde rien.
⚠ Une syntaxe fausse de ma part, qui a coûté deux essais
.+bot OverOp-T 127.0.0.1:3344 — avec des deux-points. Eggdrop attend l'adresse et le port séparés par une espace. Le bot a donc cherché une machine nommée 127.0.0.1;3333. La ligne d'échec le disait en clair, et c'est elle qui a tranché — pas une relecture du code.
4.17.3 — 15 août 2026
L'écho n'est pas revenu : le transport n'était écrit que d'un seul côté.
⚠⚠ Le défaut, et il est de moi
Les verbes SOC-PING / SOC-PONG étaient posés dans IriX-binds.tcl — donc chez IriX seulement. overop.tcl n'avait aucun bind bot.
Le premier essai réel l'a montré, à 21:33 :
IriX tcl: evaluated .tcl ::soc::botnet::ping OverOp-T, user 0.311ms
OverOp-T (rien)
journal (rien)
IriX a émis, le lien a porté la ligne, et le robot n'a rien su en faire : aucun bind pour la traiter. Pas de réponse, pas de trace, pas d'erreur. Un silence.
C'est le motif « à la jointure » du projet — un verbe qui existe d'un côté et pas de l'autre — refait le jour même où je le corrigeais pour irix-edition.tcl.
⚠ Et le banc ne pouvait pas le voir : il ne lisait que les binds d'IriX. Un transport a deux extrémités ; un banc qui n'en regarde qu'une ne garde rien.
Modifications
Robots/overop/overop.tcl—::overop::on_ping(répond) et::overop::on_pong(ne répond pas), avec leursunbindpréalables comme les trois binds voisins : un.rehashre-source ce fichier.IriX/tests/test-botnet-echo.tcl— un §4bis qui dérive lesbind botdes deux sources et croise les verbes dans les deux sens. 5 mutations neuves, 5 attrapées, dont celle qui reproduit le défaut de ce soir.
✅ Ce qui était déjà acquis, et qui tient
Le lien est monté et constaté depuis 21:29:41 — challenge, réponse, « Relié à OverOp-T », le socket ESTAB, et le module l'a vu seul au cycle suivant : « observation — 1 lien(s) monté(s) », state = linked.
4.17.2 — 15 août 2026
Le botnet avait 802 lignes et aucun verbe : rien ne pouvait circuler sur le lien. Il en a deux, et un banc qui garde la porte.
⚠⚠ Le quatrième manque du point 2
Le module déclarait les pairs, observait leur état, journalisait les transitions — et zéro bind bot, zéro putbot. Le carnet annonçait trois réglages manquants ; il en manquait un quatrième, plus lourd. Un lien monté aurait eu l'air sain et n'aurait servi à rien.
Créations
IriX/tests/test-botnet-echo.tcl— 26 constats, 7 mutations, 7 attrapées.
⚠ Pourquoi un écho, et pas l'état des robots
Faire transiter ici les ordres de prêt dupliquerait le protocole OverBot, qui les porte déjà par IRC avec UID vérifiable, plafond et corrélation. Deux canaux pour un même ordre, c'est « lequel a raison ? » à chaque divergence.
Ce que le botnet apporte et que l'IRC ne peut pas donner, c'est d'être hors du serveur : si le lien S2S tombe, les bots peuvent encore se parler. L'écho mesure la santé de ce chemin de secours, et lui seul.
⚠⚠ Un pong ne répond jamais — deux machines qui se répondent l'une l'autre n'ont aucune raison de s'arrêter. ⚠ L'horodatage est celui de l'émetteur, recopié tel quel : l'aller-retour est mesuré par une seule horloge, et ne dépend pas d'une synchronisation qui n'est jamais acquise.
⚠ Aucune migration : plutôt qu'ajouter une valeur à l'ENUM des événements — donc un ALTER, donc un risque de migration jamais appliquée — l'écho emploie les deux valeurs existantes qui conviennent.
⚠ Deux défauts de ma main, attrapés par leurs propres outils
- Mes
variableétaient écrites hors dunamespace eval, donc créées dans le namespace global. Le banc l'a dit au premier lancement. - Une mutation a survécu : le constat cherchait
[putbot, or l'appel s'écrit{ putbot— précédé d'une accolade. Le banc gardait le piège du préfixe::sans pouvoir le voir.
⚠ Ce qui n'est pas fait — le point 2 reste ouvert
botnet-nick est posé dans les cinq configurations (ce n'est pas un secret). Les deux autres manquent : la déclaration du pair et le secret partagé, que ce projet ne pose jamais par script.
Ce banc garde la forme du transport, pas son service, et il le dit à chaque passage : aucune ligne n'a circulé.
4.17.1 — 15 août 2026
Le salon gardien #!Bots! est en service : les cinq Eggdrop y siègent, tous en +B. Ce qui bloquait n'était ni le robot ni sa configuration.
✅ Constaté à 20:11:00
OverOp-H/Q/S/T joined #!Bots!
BOTSTAT : « 1 salon(s) : #!Bots! »
La page du panel dit « constaté — 5 robot(s) », et aucun écart n'est signalé.
⚠⚠ Le blocage venait du serveur, et il était MUET
conf/salons-interdits.conf est fermé par défaut — *:La création de salons est sur demande — avec six dérogations. #!Bots! n'en faisait pas partie.
⚠ L'asymétrie qui a rendu le défaut invisible : IriX y entrait par SJOIN, un lien de service qui ne passe pas cette validation ; les OverOp sont des clients ordinaires. Le salon paraissait donc sain, et aucun refus n'était journalisé côté robot. Trois journaux lus, pas un ne portait le mot #!Bots! — c'est cette absence totale de trace, et non un message d'erreur, qui a fini par désigner le serveur.
Modifications
- Septième dérogation
!#!Bots!, posée à côté de!#!Services!— son pair, autorisé pour la même raison. Appliquée parreload(SIGHUP) : aucune connexion rompue. Le serveur a confirmé « 11 exceptions ». - Les quatre
overop.confportentMODE $botnick +iBetchannel add #!Bots!. ⚠ Cette partie avait fonctionné du premier coup : les robots étaient en+ixBdès leur redémarrage.
✅ L'exemption du gardien n'a rien masqué — elle a signalé le vrai manque
Quatre lignes error à 20:07, et leur libellé est la preuve que l'exemption tenait : « absent de : #!Bots! », et non « en trop ». Le salon était devenu attendu, les robots n'y étaient pas, le mécanisme l'a dit. Zéro ligne après leur entrée.
⚠ On craignait un faux « surplus » de 288 lignes par jour et par robot ; on a obtenu un vrai « manquant », résolu tout seul. Une exemption bien placée ne rend pas un mécanisme aveugle : elle lui donne la bonne attente.
✅ Et les robots sont protégés — 20:19:36
INFO Protection : services oui, robots oui, 5 robot(s) déclaré(s)
Les cinq portent +B et figurent dans protection.conf — les deux conditions, jamais l'une sans l'autre. Le point 19 est clos.
⚠ Écrit sans accès au panel, mais pas à la main. Le compte claude existe mais son mot de passe est haché. La sortie a été d'appeler protection_ecrire(), la fonction que le panel appelle : même composition, même écriture atomique, même validation. Éditer le fichier aurait créé un second format, donc une seconde vérité — le défaut payé trois fois dans la seule journée d'hier.
4.17.0 — 15 août 2026
Ouverture de la branche 4.17. La 4.16 se ferme sur une série de versions tournant toutes autour du même défaut : du code juste que personne ne joignait. Cette première version de la branche ferme le quatrième cas, et pose le banc qui garde la jointure pour les suivants. ⚠ Le compte des versions par branche est calculé par la page ?p=versions à partir des rubriques ci-dessous — il n'est écrit à la main nulle part, et surtout pas ici.
Le module des éditions était écrit, juste, éprouvé — et personne ne le chargeait. Le trou est bouché ; surtout, un banc garde désormais la jointure.
⚠⚠ Le quatrième trou « à la jointure » de ce projet
IriX/cmds/irix-edition.tcl existait depuis le 14/08. Aucun source ne le chargeait. ::soc::edition n'existait donc pas dans le bot en service, et le jour où un module l'aurait appelée il aurait obtenu « invalid command name » — dans un catch, c'est-à-dire en silence.
⚠ Et le trou en cachait un second, trouvé en le bouchant : le module manquait aussi aux deux manifestes de déploiement de Bootstrap/overtchat-setup.sh. Le dépôt aurait marché ; une installation neuve serait morte au chargement, IriX-sql.tcl sourçant sans garde.
⚠ Aucun banc ne pouvait le voir. Web/tests/test-edition.php croise correctement les trois lecteurs, mais il atteint le Tcl par _edition-lire.tcl, qui source le module lui-même — légitime pour éprouver la lecture, aveugle sur le chargement. Un banc qui source sa cible ne peut jamais dire si le bot, lui, la source.
Créations
IriX/tests/test-modules-charges.tcl— le banc qui garde la jointure. Il dérive les trois listes (les.tcldecmds/, lessourcede l'entrée, les deux manifestes) et n'en énumère aucune : un module écrit demain entre seul dans les trois croisements. 8 mutations, toutes attrapées.
Modifications
IriX/cmds/IriX-sql.tcl— section 6quaterdecies, lesourcemanquant.Bootstrap/overtchat-setup.sh—irix-edition.tclporté dansgit.irix.filesetgit.irix.modes.sql.modules.
⚠ Ce que le banc garde, et ce qu'il ne garde pas
Trois jointures : tout module de cmds/ est chargé ; tout source vise un fichier présent ; tout module sourcé figure aux deux manifestes. Deux garde-fous refusent le vert-à-vide — une dérivation cassée rendrait une liste vide, et les croisements passeraient en tournant à blanc.
⚠ Il ne dit rien de ce que fait un module, ni de son bon fonctionnement. Il constate qu'il est joignable, rien de plus. Le hook pre-commit gardait déjà la règle des manifestes — mais il s'installe par clone, et il était absent du clone WSL le jour du constat. Une garde qui dépend d'un fichier non versionné n'est pas une garde.
⚠ Une mutation mal ciblée a failli faire accuser un banc juste
La première passe employait perl -0pi : deux mutations non appliquées (quoting), et une mal ciblée — sans /g, le remplacement touchait la première occurrence du nom, qui n'est pas celle que le constat garde. Elle s'annonçait « SURVÉCUE », c'est-à-dire le banc ne garde rien ici, sur un banc qui gardait parfaitement. C'est le piège déjà payé deux fois le 13/08.
Ce qui l'a évité : le script distingue quatre issues, et refuse de lire un verdict quand le fichier n'a pas changé. Réécrite en remplacement littéral, la passe attrape ses 8 mutations sur 8.
4.16 75 versions · 9 août 2026 → 14 août 2026 59 créations 43 évolutions 108 modifications 22 bugs corrigés 2 suppressions 3 constats 18 erreurs 44 avertissements
4.16.74 — 14 août 2026
Le chantier des éditions est ouvert, son socle est posé — et quatre passes de mutation ont été nécessaires pour comprendre ce qui protégeait vraiment.
✅ Le journal des prêts a écrit TOUT SEUL
À 19:00:00, sans personne : le passage horaire a constaté l'absence d'OverOp-S sur #membres et l'a portée au journal.
| 13 | #membres | OverOp-S | balayage automatique | absence | absent |
| premier constat — suspension dans 24 h si rien ne change |
⚠ Meilleur constat que l'essai de 18:32. MANDATON prouvait qu'un acte commandé s'écrit ; celui-ci prouve que le mécanisme écrit quand personne ne regarde. Le result vaut absent, distinct d'error : une absence est un état constaté, pas une panne.
Le point 14 est clos
require_grade … operateur éprouvé dans les deux sens, une seule connexion : Accès refusé. sans auth, MODE #accueil +o ClaudeOps avec. ⚠ Le premier sens compte autant que le second — un !op qui marche ne prouve pas que la garde tient, seulement qu'elle n'a pas mordu.
Créations — Phase 1 du chantier des éditions
Config/config.json→project.edition. ⚠ Ce fichier est déjà exclu de la surveillance de mise à jour : l'amont ne l'écrase jamais, ce qui en fait le seul bon endroit pour un état propre à l'installation.Web/inc/edition.php,IriX/cmds/irix-edition.tcl,Tools/edition.sh— un lecteur par langage, et trois seulement. « Un composant qui DÉDUIT son édition finira par se tromper. »Web/tests/test-edition.php— 19 constats, qui croisent les trois.IriX/tests/_edition-lire.tcl— l'outil que le banc appelle. Tiret bas en tête pour que le lanceur ne le prenne pas pour une campagne muette.
⚠⚠ Quatre passes de mutation, deux commentaires faux écrits en chemin
Le motif doit éviter la note _edition, qui cite « beta » et « payante ». J'ai écrit que les guillemets protégeaient, puis — après une mutation restée verte — que c'était la forme [a-z]*. Les deux sont vrais, et chacun suffit seul : il faut casser les deux gardes ensemble pour que la note soit lue.
⚠ Une mutation qui ne rougit pas ne prouve pas que le code est solide. Elle peut aussi dire que la mutation ne mord pas, ou que le cas de test compare deux valeurs identiques — un cas plaçait « payante » des deux côtés, si bien que la lecture juste et la fausse rendaient la même chose. Les deux se sont produits ici, et c'est en mesurant ce que sed rendait vraiment que la réponse est venue, pas en raisonnant.
⚠ Ce que le banc n'attrape pas, et c'est écrit dans son en-tête
Son bac à sable n'éprouve que le lecteur bash. Casser les deux gardes du motif Tcl le laisse vert. Le croisement des trois limite les dégâts, mais seulement sur la configuration réelle.
Décisions de SerVuS
- Ouvrir tout le chantier des éditions, contre l'avis écrit au registre — la réserve lui a été présentée avec son coût.
- Question 5.5 : dix installations au plus. Elle ferme le §6 plus qu'elle ne l'ouvre : la licence MIT impose de vendre le service, rien à payer pour distribuer, serveur de licences sans objet. ⚠ Un seul point lourd survit — héberger les réseaux des clients est un autre métier, à dix comme à mille.
- Laisser
ircopstel quel dans la table des grades.
70 bancs au vert, 3895 constats.
4.16.73 — 14 août 2026
Le journal des prêts entre en service — et il aurait posé deux lignes pour une seule fin.
⚠⚠ Un doublon, trouvé avant le premier essai
MANDATFIN journalisait l'acte fin de son côté, écrit le 05/08 quand terminer ne journalisait pas encore. Depuis la 4.16.71 elle le fait : garder les deux aurait posé deux lignes pour une seule fin, dont une sans auteur.
Corrigé au bon endroit : l'auteur est passé à terminer, et le journaliser externe est retiré. Une porte, une rédaction — c'est la règle qui a fait mettre le journal dans terminer et basculer plutôt que chez chacun de leurs appelants, et elle vaut aussi pour les appelants qui journalisaient déjà.
⚠ Trouvé en cherchant comment provoquer un acte pour l'essai, pas en relisant le code. Le geste qui allait éprouver le mécanisme a montré son défaut avant lui.
Modifications
MANDATON/MANDATOFFpassent l'auteur et la raison àbasculer. Sans eux, le journal notait « automatique » pour un geste que quelqu'un avait décidé, et l'on ne distinguait plus une reprise à la main du balayage horaire.MANDATFINpasse l'auteur àtermineret ne journalise plus lui-même.
⚠ Une asymétrie connue, laissée telle quelle
L'accord reste journalisé par ses deux appelants — consommer pour le chemin du panel, la commande MANDAT pour celui de l'IRCop — et non par accorder. Ce n'est pas un doublon : les deux chemins s'excluent. Le déplacer dans accorder serait plus cohérent, mais c'est une refonte que rien n'exige aujourd'hui.
✅ Contre-épreuve du point 16, gratuite
IriX a été redémarré à 18:26:25, le même jour que le premier rattrapage. Le scan quotidien n'a pas rejoué — une seule paire « démarré / terminé », celle de 17:13:47. La garde du jour calendaire tient sur un vrai redémarrage, ce que le banc seul ne pouvait pas prouver.
✅✅ CONSTATÉ EN SERVICE — 18:32:08
IriX redémarré à 18:30:19 avec le code corrigé, puis un seul essai : MANDATON #membres envoyé à OverBot par ClaudeOps, en TLS sur 6697.
OverBot → « Prêt de #membres rétabli. »
| 12 | #membres | OverOp-S | ClaudeOps | reprise | ok | MANDATON par ClaudeOps |
Et vu dans la page, rendue depuis le code de production : « 18:32:08 · Rétabli · #membres · OverOp-S · ClaudeOps », filtre « Rétabli (1) ». Le journal passe de 9 lignes figées au 12/08 à 10, la dernière du jour. Une seule ligne pour l'acte : le doublon avait été retiré à temps.
⚠ Le refus n'est pas éprouvé en réel et ne le sera pas facilement : basculer journalise result=error quand la base refuse l'UPDATE, un cas qu'on ne provoque pas sans casser quelque chose. Un salon sans prêt sort avant l'écriture et ne journalise rien — correct, mais cela n'éprouve pas cette branche. Elle reste couverte par le banc seul, et c'est une limite à connaître.
⚠ Note d'exploitation : sudo -n systemctl restart irix est refusé. La règle sudoers écrit la commande en entier, sans joker — il faut /usr/bin/systemctl restart irix.service, chemin absolu et suffixe compris.
4.16.72 — 14 août 2026
« 66 commandes à renommer » était faux — et c'est en le mesurant avant de le faire qu'on s'en est aperçu.
SerVuS a demandé de finir le renommage des grades sur la foi du tableau écrit en 4.16.71. Il n'y avait rien à renommer. Le seul défaut était que cmd_rangs() ne déclarait pas operateur ni proprietaire.
Ce que la mesure a montré
En lisant les gardes réelles plutôt que les noms :
| Ce qui garde la commande | Combien | Le rang qui lui revient |
|---|---|---|
::soc::require_grade | 60 — admin 40, operateur 12, user 3, proprietaire 3, voice 2 | le grade comparé |
require_ircop · require_admin · botisircops | 57 occurrences | ircops — ces gardes interrogent le serveur |
matchattr $hand n | 1 (syncowner) | owner — l'owner Eggdrop |
Zéro require_grade … netadmin dans tout le projet. Les trois occurrences trouvées sont des commentaires — et le premier grep ne les excluait pas, ce qui a failli produire un faux défaut de plus.
ircops n'est donc pas un ancien nom de netadmin : c'est le nom d'une autre autorité. Le renommer aurait fait rougir test-grades.php, qui dérive ircops de ces gardes — il aurait fallu truquer le banc pour faire passer le renommage. ⚠ Le nom suit l'autorité qui décide, pas l'inverse.
Modifications
Web/inc/commandes.php—cmd_rangs()déclareoperateur(3) etproprietaire(5) ;opest retiré, plus aucune commande ne le portait.- Les deux clés qui ne sont pas des grades SQL disent enfin ce qu'elles sont :
ircops« vérifié auprès du serveur, jamais par un grade en base », etowner (Eggdrop)« flagndu userfile, sans rapport aveccsowner». Leur description mentait plus discrètement que le rouge.
Bugs corrigés
test-commandes.phpétait rouge depuis 15:41 — le seul échec sur 69 bancs, et il l'était avant la séance. 15 commandes portaient un grade que la table des libellés ne connaissait pas. Il ne cassait rien en service : cette table libelle et trie, elle ne garde aucune porte.
⚠ La mutation qui vaut plus que le reste
Passer syncowner de owner à proprietaire fait rougir test-grades.php. Le renommage aveugle aurait donc été arrêté par un banc, mais seulement après avoir été écrit. Mesurer les gardes coûte dix minutes ; défaire un renommage en coûte davantage.
Six mutations attrapées, page publique rendue hors navigateur (12 tableaux, 207 lignes, zéro notice), et 69 bancs au vert, 3874 constats.
4.16.71 — 14 août 2026
Le journal des prêts ne gardait presque rien — et c'est une suspension partie sous nos yeux qui l'a montré.
Le constat qui a tout déclenché
Le point 11 attendait de voir partir la suspension automatique d'un prêt non servi. Elle est partie le 14/08 à 18:00:00, exactement comme annoncé :
18:00:00 [WARN] prêt : #membres SUSPENDU — OverOp-S n'y est pas entré depuis 24 h.
18:00:00 [INFO] prêts : 0 marqué(s) absent(s), 0 revenu(s), 1 suspendu(s)
GLOBOPS :[MANDAT] #membres : prêt SUSPENDU — … MANDATON #membres le rétablit.
⚠ L'échéance avait été avancée d'une heure, SerVuS ayant demandé à 17:30 de ne pas attendre 19:00. absent_depuis a été reculé de 19:00 à 17:55 sur la seule ligne d'essai — ni le seuil ni le code n'ont bougé, et le déclenchement est resté entièrement automatique. 17:45 avait été demandé et n'était pas atteignable : le contrôle est porté par un bind time horaire, qui ne part qu'à la minute :00.
⚠⚠ Et la suspension n'a laissé AUCUNE trace en base
${t}_mandat_log n'a pas bougé. Le geste le plus lourd que ce mécanisme sache faire — retirer ses pouvoirs à un prêt — était invisible de tout ce qui lit la base, donc du panel. Il n'existait que dans deux fichiers de journal et dans un GLOBOPS que personne ne relit.
On ne l'a pas déduit : on l'a regardé ne pas s'écrire.
Le croisement des 32 procédures du module contre leurs appels à journaliser a confirmé l'étendue : deux journalisaient. Ni la suspension, ni la reprise, ni la bascule de charge, ni la fin manuelle, ni les deux ordres réseau, ni les constats d'absence.
⚠ Le commentaire de journaliser porte « TOUJOURS appelée » depuis l'origine. Il était déjà faux le 05/08, corrigé à moitié ce jour-là, et encore faux neuf jours plus tard. Un commentaire n'est pas une garantie.
Créations
Web/inc/mandat.php— le journal des prêts et l'état de chacun, vus du panel. Aucune écriture : le journal s'écrit côté bot, et là seulement.Web/tests/test-mandat-journal.php— 18 constats, six mutations attrapées. Il dérive les actes et l'ENUMresultdu source Tcl et les croise avec les libellés de la page. Sans lui, un acte ajouté côté bot serait enregistré, visible dans « Toutes », et introuvable au filtre.IriX/tests/test-bindtime.tcl— 8 constats, cinq mutations attrapées. Toutbind timedoit déclarer son rattrapage sur une ligne# RATTRAPAGE:.
Modifications
irix-mandat.tcl— le journal s'écrit dansbasculeretterminer, les deux portes par lesquelles un prêt change d'état, plusinstaller,retireret les deux constats d'absence. Aux portes, et non chez les appelants : les y laisser aurait donné autant de rédactions que de chemins. ⚠ Les refus sont journalisés aussi.paretpourquoisont optionnels — un paramètre obligatoire aurait imposé de réécrire tous les appelants, et c'est ainsi qu'on oublie le seul qui comptait.- Neuf actes :
accord·fin·suspension·reprise·bascule·absence·retour·entree·sortie. ⚠entreeetsortiedisent qu'un ordre est parti, jamais que le robot est entré ou sorti. Web/pages/robots.php— deux sections neuves, avec un filtreadistinct de celui des demandes (r) : partagé, filtrer l'un aurait vidé l'autre.IriX-binds.tcl— chaquebind timedéclare son rattrapage. Le point 15 est tranché par l'option (c), l'assumer : le gel coûte un retard, jamais une donnée, et les trois travaux du passage horaire comparent du temps.test-mandat.tcl— 199 constats. Le talon debasculersuit la vraie signature et garde ce qu'on lui passe : l'accepter sans le garder aurait fait passer au vert un constat qui ne lit rien.
⚠ Deux confirmations mensongères attrapées en chemin
- Le banc de croisement prenait
"suspendu"pour un acte — c'est l'issue, lue dans la condition d'un ternaire. Un croisement qui extrait trop accuse le code juste, et l'on est à deux doigts d'ajouter à la page une entrée qui n'aurait jamais dû exister pour faire taire le rouge. - Le rendu de page annonçait quatre notices PHP. Elles venaient d'un
echodu script de mesure, envoyé avant lesrequire— pas du panel. Sansecho, zéro notice. Soupçonner l'outil avant le code. test-bindtime.tclextrayait le corps d'admin::scanpar expression régulière et rendait 266 146 caractères, la moitié du fichier : son constat passait au vert en cherchant dans presque toutIriX-sql.tcl. Remplacé par le comptage d'accolades detest-mandat.tcl. Puis une mutation a montré que le constat suivant se contentait de la gardeinfo commands, qui n'appelle rien — le banc gardait la porte en regardant la serrure.
⚠ Ce qui n'est pas encore en service
Le bot tourne le code chargé à 17:13 : la journalisation n'écrit pas encore. Au prochain démarrage, MANDATON #membres doit produire une ligne acte=reprise, et il faudra la regarder dans la page. Point 17 de CHANTIERS.md.
4.16.70 — 14 août 2026
Le scan quotidien a tourné pour la première fois — et personne ne l'a provoqué.
Le point 16, ouvert à 14:43 le jour même, est clos par constat à 17:13:47. C'est la seule façon dont il pouvait l'être : sa ligne « Pour fermer » disait qu'un appel forcé à la main ne vaudrait pas preuve — il aurait montré que la procédure marche, pas que le rattrapage part.
Le constat
La machine a redémarré d'elle-même à 17:13:12 (un vrai redémarrage : boot -1 clos à 17:12:37), le service est parti à 17:13:14, et 33 secondes plus tard le scan tournait. C'était le premier démarrage postérieur au déploiement de 14:43:34 — celui de 09:46 lui était antérieur, le code n'existait pas encore.
2026-08-14T17:13:47 [INFO] scan quotidien démarré
2026-08-14T17:13:47 [INFO] scan quotidien terminé
daily_last_run porte 2026-08-14 dans IriX_soc_settings. Le cycle étant de 30 s (rehash_interval), une vingtaine de passages ont suivi sans rien refaire : un démarré, un terminé, une purge quotidienne, et rien de plus. La garde du jour calendaire tient.
⚠ La consigne de recherche était fausse, et elle aurait fait conclure à tort
Le point disait de chercher « dans IriX-sql.log seulement ». La trace de la purge IA est écrite par putlog, donc dans IriX/runtime/IriX.log :
[14/08 17:13:47] SOC IA: purge quotidienne effectuee
Un grep obéissant aurait conclu que la purge n'avait pas tourné. Les deux journaux servent, et ils ne portent pas les mêmes lignes.
Ce que la passe a fait — presque rien, et c'est mesuré
Le point redoutait qu'une purge muette depuis le 27/07 « ait beaucoup à faire d'un coup ». Chaque table était en deçà de sa rétention : chat_hist 54 lignes depuis le 09/08 (30 j), ia_inbox/ia_outbox 9 et 9 depuis le 09/08 (7 j), ia_log 9 (90 j), ia_queue vide, ia_tokens à 0. Zéro ligne au-delà de son seuil.
hist::purger n'a rien retiré, et son silence le prouve : elle ne journalise que si avant ne apres. Confirmé par les identifiants — chat_hist va de 1 à 54 sans trou. ⚠ ia_inbox commence à l'id 10, mais un trou d'identifiants prouve une suppression, jamais sa date : le corpus a été purgé à la main deux fois auparavant.
Aucun courriel d'expiration n'est parti — c'était le risque assumé au déploiement. Aucun pseudonyme n'était échu ; la dernière ligne d'IriX_mail_log date du 05/08.
⚠ Ce qui n'est pas encore éprouvé
La passe n'avait rien d'échu à retirer : les DELETE n'ont donc pas été éprouvés en charge. Au plus tôt le 08/09/2026, quand chat_hist dépassera 30 jours, vérifier que hist: purge — N ligne(s) retirée(s) apparaît dans IriX-sql.log.
Modifications
Markdown/CHANTIERS.md— point 16 sorti des défauts ouverts et porté dans les travaux, avec son constat. Il ne reste qu'un seul défaut ouvert : le 15. Son affirmation « déployé sans rechargement, IriX tourne l'ancien code » a été datée : elle était vraie à 14:43 et fausse à 17:13.Markdown/IRIX.mdetMarkdown/DATABASE.md— les deux annonçaient au futur ce qui est arrivé. ⚠ DATABASE.md gardait « chaque nuit » comme cible : la remise à zéro a lieu à la première occasion du jour, soit le démarrage du bot au réveil de la machine, pas minuit.- Point 15 — sa piste (b) « rattraper au réveil » n'est plus une hypothèse. Elle ne couvre que le passage quotidien : le passage horaire reste perdu, et le gel n'est pas corrigé. Le point reste ouvert.
4.16.69 — 14 août 2026
Le registre passé en revue point par point — et deux défauts qui duraient depuis des semaines sans que rien ne les signale.
Les seize points de CHANTIERS.md ont été mesurés dans la journée. Cinq disaient faux, et deux défauts réels ont été ouverts.
⚠⚠ Le scan quotidien n'avait jamais tourné
::soc::scan::daily est lié par bind time - "00 00 * * *". Le service est arrêté chaque soir vers 21:00 et relancé vers 09:00 : minuit tombait dans le trou tous les jours, et un bind time manqué n'est jamais rejoué par Eggdrop.
Quatre comptages indépendants, tous à zéro : aucune ligne à minuit dans quatorze archives, aucun « scan quotidien » dans journalctl depuis le 27/07, aucun « deauth quotidien », et des heures journalisées allant de 08 à 23 — jamais 00 à 07.
Trois travaux de fond ne partaient donc jamais : la purge de l'historique, celle du corpus IA, et l'expiration des pseudonymes.
⚠⚠ Et le rattrapage a révélé un défaut dans ce qu'il allait faire partir
La déauthentification quotidienne faisait onchan sur [channels] seulement — le raisonnement périmé corrigé dans ::soc::auth::scan le 03/08. Un utilisateur identifié qui ne partage aucun salon avec IriX est parfaitement connecté, et se voyait déauthentifié. Le défaut avait survécu parce que ce scan ne partait pas : rien ne pouvait le manifester, et il aurait frappé de vrais utilisateurs au premier rattrapage.
Créations
- Rattrapage du scan quotidien dans
IriX-sql.tcl—daily_rattrapage,daily_due,daily_derniere,daily_marquer, etdaily_corpsextrait dubind time. Motif de::soc::logrottransposé : unutimerqui se réarme, une garde sur le jour calendaire, une mémoire en base (daily_last_rundanssoc_settings). Trois jours manqués ne donnent qu'une reprise. - Contrôle des tableaux dans
Web/tests/test-markdown.php— une suite de lignes en « | » doit rendre un<table>, et un seul. Compte dérivé du source, aucun nombre en dur. - Repli PHP par WSL dans
Tools/git-hooks/pre-commit.
Modifications
require_gradeparle le vocabulaire du réseau :op→operateur,owner→proprietaire,ircops→netadmin. Sans accent ni tiret — ce sont des clés comparées parlsearch.admin,useretvoicene changent pas.Web/inc/commandes.phpa suivi.IRIX.md,DATABASE.md,RESEAU.md,GRADES.md: corrigés de ce que la mesure a démenti.
Suppressions
- La déauthentification quotidienne de
daily_corps.::soc::auth::scanfait ce travail toutes les 60 secondes, avec une garde de démarrage que le geste retiré n'avait pas, et en consultant le registre du lien.
Bugs corrigés
- Un tableau pouvait disparaître du site en silence. Retirer un séparateur
|---|---|ne casse aucune paire de balises : le tableau ne naît pas, sa ligne retombe en paragraphe, et ses barres s'affichent au lecteur. Le banc restait vert. Trouvé par mutation, et il a attrapé son auteur le jour même. - Tout PHP commité depuis le poste Windows passait sans contrôle de syntaxe : le hook renonçait faute de
php. Il passe désormais par WSL, et le dit. syncownerallait être renommé à tort — son « owner » est le flagnd'Eggdrop, pas un grade SQL. Attrapé partest-grades.php.
Évolutions
18555et8123identifiés :go2rtc(module WebRTC) et Home Assistant, tous deux dans un conteneur ennetwork_mode: host— leurs ports ne sont pas redirigés, ils sont ceux de la machine, IPv6 publique comprise.- Le point 1 (amorce de l'IA) et le point 5 (lot 3) étaient fermés et annonçaient du travail : reconstatés, puis marqués clos.
⚠ Avertissements
- Écrit n'est pas en service. Le rattrapage et les grades renommés sont sur le disque de la prod, pas en mémoire : le bot tourne encore avec l'ancien code. Ils n'entrent en vigueur qu'à son redémarrage, et c'est là seulement que les constats pourront se faire.
- Le gel de WSL n'est pas corrigé (point 15). La veille de Windows fait perdre des passages horaires ; le croisement montre que le projet compare partout du temps et non des passages, donc le gel coûte des passages, pas des données.
- Le point 10 attend un geste de l'exploitant — un mot de passe par pseudonyme, qui ne peut pas être automatisé. La procédure est vérifiée et son ordre écrit.
4.16.68 — 13 août 2026
Les fichiers de sauvegarde ne se servent plus — et la règle évidente aurait été sans effet.
Après la fuite de la 4.16.67, une règle pour qu'elle ne puisse pas recommencer.
⚠⚠ <LocationMatch> et non <FilesMatch>, et c'est tout l'enjeu
Le réflexe est d'écrire un <FilesMatch> : c'est la directive qu'on trouve partout pour interdire des extensions. Elle n'aurait rien bloqué.
Les deux vhosts publics ne servent aucun fichier — ils relaient tout à php -S par un ProxyPass /. <FilesMatch> porte sur des fichiers du système de fichiers local : il n'aurait rien eu à inspecter. La règle aurait été écrite, relue, crue efficace, et n'aurait bloqué personne.
<LocationMatch> porte sur l'URL, et l'autorisation est évaluée avant le mapping du proxy. ⚠ Un constat du banc garde précisément ce choix — c'est le genre de détail qu'une relecture rapide « corrigerait » dans le mauvais sens.
⚠ Un constat de ce banc rougissait sur une configuration juste
Il faisait str_contains($src, '<FilesMatch') — et trouvait le mot dans le commentaire qui explique pourquoi on ne l'emploie pas. C'est le travers « mentionné, pas appelé » du carnet, pris à l'envers : ici, mentionner ne veut pas dire employer.
Le motif exige désormais un début de ligne, espaces compris, sans dièse — puisque c'est exactement ce qu'Apache lui-même ignore. Deuxième fois aujourd'hui qu'un constat confond une chaîne avec une directive active.
Le motif, éprouvé dans les deux sens
⚠ Une règle qui bloque trop casse le site, et c'est une faute autant qu'une règle qui ne bloque rien. Le banc éprouve donc les deux : neuf noms qu'elle doit refuser — dont le fichier qui a réellement fuité — et dix qu'elle doit laisser passer, y compris /.well-known/acme-challenge/…, sans quoi le renouvellement du certificat tomberait.
Créations
Web/tests/test-apache-sauvegardes.php— 30 constats. Il vérifie aussi, par dérivation du disque, qu'aucun fichier de sauvegarde ne traîne aujourd'hui dans la racine servie : la règle est une seconde barrière, pas la première.
⚠ À déployer à la main
sudo ne couvre pas apache2 — vérifié. La copie et le rechargement appartiennent donc à l'exploitant, et apache2ctl configtest doit précéder le reload.
4.16.67 — 13 août 2026
Le code du panel a été téléchargeable depuis Internet, et un conseil envoyait chercher une chose déjà faite.
⚠⚠ Un fichier de sauvegarde dans la racine servie
Web/public/admin.php.avant-connexions → HTTP 200, 37 160 octets
sur 8080, 8880 ET 8443
Une copie du point d'entrée du panel, laissée dans Web/public/. Son extension n'étant pas .php, elle n'était pas exécutée : elle était servie en clair. Et 8880 / 8443 sont les ports ouverts vers l'extérieur.
⚠ Pas une fuite de secrets — aucun mot de passe ni empreinte en dur, les occurrences ont été comptées sans qu'aucune valeur soit affichée. Mais toute la logique du panel : routes, actions, contrôles de niveau, requêtes.
Fermé le jour même par l'exploitant, puis vérifié : 404 sur les trois ports, et le site répond toujours.
Les trois choses à en retenir, portées dans SECURITY.md :
- ⚠ Une extension inconnue n'est pas une protection, c'est le contraire. Un
.phpest exécuté et ne rend que sa sortie ; un.php.avant-quelquechoseest rendu tel quel. Renommer un fichier sensible pour le « mettre de côté » l'expose davantage que de le laisser en place. - ⚠ Le dépôt ne protège de rien ici : ces fichiers n'étaient pas suivis par git, donc absents de GitHub et invisibles à tout contrôle qui lirait le dépôt. Un contrôle qui lit le dépôt ne voit pas ce que le serveur sert.
- ⚠ Un essai au mauvais endroit ne prouve rien : le premier contrôle interrogeait
/inc/…, qui rend 404 parce que la racine servie estWeb/public/. Il concluait à tort que rien n'était exposé.
Le conseil qui ne regardait pas s'il servait
panel-account.php conseillait, à chaque création de compte, de retirer le repli PANEL_USER / PANEL_PASS_HASH. Il l'affichait sans condition — or il est retiré depuis longtemps, dans les deux copies. Le message envoyait donc chercher une chose déjà faite.
⚠ Le contrôle teste defined(), jamais la présence du texte : les constantes figurent en commentaire dans le fichier, et un grep les y trouverait toujours.
Et une nuance ajoutée à l'analyse d'exposition
SECURITY.md décrivait la surface d'attaque en raisonnant sur les redirections de ports — or elles ne valent que pour l'IPv4. Mesuré : le serveur écoute sur ip * pour 6667 et 6697, la distribution porte une IPv6 publique et n'a aucun pare-feu. La page le dit désormais.
Modifications
Markdown/SECURITY.md— la leçon des fichiers de sauvegarde, et la nuance IPv6.Tools/panel-account.php— conseil conditionnel.Web/tests/test-panel-account.php— 15 constats.
4.16.66 — 13 août 2026
Le désinstalleur appelait la sauvegarde avec une option qui n'existe pas.
Écrit une heure plus tôt, il passait --avec-base à backup-overtchat.sh. L'option s'appelle --with-db. L'autre script serait donc mort sur « Option inconnue », et le désinstalleur se serait arrêté là — toujours, sans jamais pouvoir aboutir autrement qu'avec --sans-sauvegarde.
⚠ Le garde-fou a fonctionné : le script s'arrête si la sauvegarde échoue. La conséquence était donc une impossibilité, pas une destruction. Mais un outil qui ne peut jamais aboutir n'est pas un outil.
⚠ Aucun banc ne pouvait le voir, et il faut le dire
Ce chemin vient après la saisie du mot de passe, que le banc ne franchit pas — personne ne doit taper le sien dans un banc. Les 26 constats étaient verts, les 6 mutations détectées, et l'appel était faux.
C'est le travers « appeler ce qui n'existe pas » du carnet, sur une option plutôt qu'une procédure. ⚠ Et il est tombé en lisant l'aide de l'autre script, pas en relisant celui-ci : j'avais écrit l'option en français sans la vérifier.
Le constat qui l'attrape, et qui est dérivé
Le banc lit désormais les options que desinstaller.sh passe à backup-overtchat.sh, et celles que ce dernier accepte, dans son analyseur d'arguments. Il croise les deux. Une option ajoutée demain entre toute seule.
⚠ Avec son témoin : il vérifie qu'il sait reconnaître une option inexistante. Sans lui, une extraction cassée rendrait deux listes vides et « tout va bien ».
Modifications
Tools/desinstaller.sh—--with-db.Tools/tests/test-desinstaller.sh— 29 constats, 7 mutations toutes détectées, dont le vrai bug remis à l'identique.
4.16.65 — 13 août 2026
Créer un compte faisait taper le mot de passe deux fois, puis mourait sur une trace SQL.
Trouvé en créant le second compte d'administration, sur une vraie installation :
PHP Fatal error: Uncaught PDOException: SQLSTATE[23000]:
Duplicate entry '…@free.fr' for key 'uk_web_users_email'
panel-account.php vérifiait que le login était libre. Jamais l'adresse — alors que la table porte uk_web_users_email, unique. La commande allait donc jusqu'au bout, faisait saisir le mot de passe deux fois, et mourait à l'INSERT.
⚠ Le coût n'est pas cosmétique. Quelqu'un tape son mot de passe pour rien, et lit « Integrity constraint violation » au lieu de « cette adresse est déjà celle du compte SerVuS ». Un outil qui refuse tard refuse mal.
La règle, et elle était déjà écrite ailleurs
Refuser tôt évite de poser des questions à quelqu'un dont on sait déjà que la réponse ne servira pas. C'est mot pour mot ce que le désinstalleur écrit à propos du mot de passe, publié une heure plus tôt le même jour — et cet outil-ci faisait l'inverse. Le contrôle passe donc avant le niveau et le mot de passe.
⚠ Le try/catch reste malgré tout : entre la lecture et l'écriture, un autre processus peut prendre l'adresse. C'est rare — et c'est exactement pour cela qu'on n'aurait pas de message si on ne l'écrivait pas.
Créations
Web/tests/test-panel-account.php— il lance la vraie commande avec une adresse dérivée de la base, jamais recopiée, et vérifie qu'elle refuse avant de demander le mot de passe. ⚠ Il ne fournit d'ailleurs aucun mot de passe sur son entrée : si l'outil en demandait un, il ne pourrait pas aboutir. ⚠ Et il compte les comptes avant/après — aucun n'est créé.
4.16.64 — 13 août 2026
Un désinstalleur complet, protégé par le mot de passe administrateur — demandé par SerVuS.
Tools/desinstaller.sh retire tout ce qu'une installation pose : les unités systemd (découvertes depuis Config/systemd/, pas énumérées), les fichiers système, les sites Apache, les répertoires des robots, la base, et enfin la racine.
⚠⚠ Il ne fait rien par défaut
Sans --pour-de-vrai, il montre ce qu'il supprimerait et s'arrête. Un désinstalleur qui détruit au premier lancement n'est pas un outil, c'est une arme — et une faute de frappe suffit à le lancer. À blanc, il ne demande même pas le mot de passe : s'il le demandait, quelqu'un le taperait « pour voir ».
⚠ Les sauvegardes survivent, et c'est ce qui rend l'acte réversible
~/Sauvegardes n'est jamais touché sans --avec-sauvegardes écrit à la main. Les effacer avec le reste rendrait la désinstallation définitive — or c'est précisément le filet de celui qui s'est trompé. Le script produit d'ailleurs une archive avant de supprimer quoi que ce soit, et s'arrête si elle échoue.
Les cinq refus, dans l'ordre
- Quatre fichiers témoins — un dossier qui porte le même nom n'est pas une installation ;
- la racine n'est pas un répertoire de premier niveau, ni le répertoire personnel ;
- le mot de passe d'un administrateur du panel, vérifié par
password_verifycontre la base. ⚠ Saisi masqué, jamais journalisé, jamais passé en argument :psest lisible par tous. Il transite par l'entrée standard. ⚠ Et si la base est injoignable, on ne désinstalle pas : on n'agit pas sans avoir pu vérifier ; - le nom de l'installation, tapé à la main — « oui » est un réflexe, taper le nom demande de lire ce qu'on détruit ;
- une option inconnue est refusée, jamais ignorée : une faute de frappe sur
--pour-de-vraine doit pas se lire comme un consentement.
⚠⚠ Ce que la passe de mutation a trouvé : deux gardes sur quatre ne servaient à rien
Le premier jet portait une liste noire de chemins (/home, /etc, /tmp…) et un test de chaîne vide. Les deux ont survécu à leur mutation : réduire la liste à / seul laissait le banc entièrement vert. La raison est simple — tous ces chemins sont des répertoires de premier niveau, déjà refusés par la règle qui suit. La chaîne vide aussi.
Elles ont été retirées, pas gardées « au cas où » : du code qui ne protège rien donne l'illusion d'une ceinture de plus, et c'est pire que de ne pas l'avoir.
⚠ Et le banc avait d'abord fait tomber la garde elle-même : elle refusait tout chemin de moins de douze caractères — or /opt/oc est une installation parfaitement légitime. On compte désormais les segments.
⚠ Ce qui n'est pas muté, et pourquoi
Le garde-fou du mode à blanc. Une mutation qui le lèverait ferait tenter de vraies suppressions pendant le banc. On ne casse pas un cran de sûreté pour vérifier qu'il existe.
Créations
Tools/desinstaller.shTools/tests/test-desinstaller.sh— 26 constats, 6 mutations toutes détectées. Les essais montent de faux arbres dans un répertoire jetable ; aucun ne touche à l'installation réelle.
4.16.63 — 13 août 2026
Passe d'anomalies : deux pages d'installation annonçaient comme absent ce qui tourne tous les jours.
⚠⚠ « Aucune sauvegarde n'a jamais été produite »
ARCHITECTURE.md l'écrivait en toutes lettres, et INSTALL.md disait la même chose en plus court : les deux derniers timers « pas encore installés », et pour la sauvegarde « la phrase secrète n'existe pas ».
Mesuré le 13/08 : les cinq timers du dépôt sont enabled et active, tous les .service du dépôt sont installés, et la sauvegarde de 16:29:40 a produit une archive de 4,6 Mo — intégrité vérifiée par déchiffrement, copie hors machine sur le disque de la Freebox vérifiée, double rotation, ExecMainStatus=0. La phrase secrète existe donc nécessairement : sans elle, rien ne se chiffrerait.
⚠ Ces deux pages contredisaient BACKUP.md et CHANTIERS.md, qui portent la sauvegarde comme constatée depuis le 11/08 — restaurée pour de vrai, copiée hors machine le 12/08. Deux documents du même dépôt disaient le contraire l'un de l'autre, et ce sont ceux qu'un installateur suit qui avaient tort.
Les deux tableaux listaient aussi trois robots overop@ là où il y en a quatre depuis le 11/08.
⚠ Le même grade portait deux noms selon l'écran
La page d'administration d'un salon disait « Helpeur » depuis la 4.16.62, et le webchat disait encore « demi-opérateur » — deux mots pour un seul grade, selon l'endroit où on le lisait. Corrigé dans chat.js. ⚠ La clé ne change pas : g-halfop en CSS et le rang 2 restent tels quels.
⚠ Ce que cette passe a écarté, et qui n'était pas une anomalie
unrealircd.service paraissait cité par la doc sans être installé. C'était mon filtre qui était incomplet — il ne cherchait pas « unreal ». L'unité existe, elle est disabled, et la doc le dit déjà. ⓘ De même, ${t}_raw_log est absente de la base parce qu'elle est conditionnelle (raw_log=1), ce que DATABASE.md annonce.
Un outil de mesure incomplet accuse un innocent aussi sûrement qu'un document périmé.
Modifications
Markdown/INSTALL.md,Markdown/ARCHITECTURE.md— l'état réel, plus la commande qui le dérive au lieu de le lire.Markdown/WEB.md— le tableau des niveaux du panel distingue désormais la clé (inchangée) du nom rendu (4.16.61).Web/public/assets/chat.js— « Helpeur » dans les boutons de grade.
4.16.62 — 13 août 2026
Les grades de salon portent le vocabulaire — un seul mot changeait.
Deuxième étape de l'unification. CHANACCESS_LIBELLES est ce qui s'affiche vraiment à un propriétaire de salon, dans salonadmin.php : le grade posé, la liste des accès, et le menu qui sert à en poser un.
Trois libellés sur quatre étaient déjà ceux du vocabulaire — Opérateur, Propriétaire, Voix. Seul halfop a changé : « Demi-opérateur » devient « Helpeur ».
⚠ Une étape annoncée qui ne sert à rien, et qu'on ne fera donc pas
Le plan prévoyait aussi de renommer ${t}_grades.description. Mesuré : personne ne la lit. Son seul usage dans tout le projet est l'INSERT qui la pose à la création de la table — aucun SELECT, ni en Tcl, ni en PHP.
La renommer aurait été un travail invisible, avec le risque d'une migration pour rien. Elle est retirée du plan plutôt que faite par acquit de conscience.
⚠ Les clés ne bougent pas, et le banc l'exige
voice, halfop, op, owner restent les clés : ce sont celles de IriX_grades, et celles que chanaccess écrit en base. Renommer une clé casserait les grades déjà posés — une mutation le vérifie, et le banc rougit.
⚠ Deux constats qui existaient ne suffisaient pas
Le banc gardait déjà la correspondance des clés et des libellés : chaque grade a un libellé, aucun libellé n'est orphelin. Ces deux-là seraient restés verts si tous les mots avaient changé — or c'est le mot qui a été tranché. Quatre constats nomment donc désormais chaque libellé, et un cinquième bannit explicitement l'ancien « Demi-opérateur », pour qu'il ne revienne pas par une fusion.
Modifications
Web/inc/chanaccess.php—halfops'affiche « Helpeur ».Web/tests/test-chanaccess.php— 48 constats, 5 mutations toutes détectées.
4.16.61 — 13 août 2026
Les niveaux du panel portent le vocabulaire du réseau — première étape de l'unification des grades.
Décidé par SerVuS le jour même : Net-admin · Admin · Chef-de-bot · Opérateur · Xop · Helpeur, partout où un grade s'affiche. Le panel passe en premier parce qu'il est isolé — il ne touche ni require_grade, ni les colonnes cs*, ni l'exemption d'expiration.
⚠ Ce qui a rendu ce renommage sûr, et qu'il fallait vérifier avant
level_name() rend un libellé, jamais une clé. Ses trois seuls appelants l'insèrent dans un message d'erreur ou dans le journal d'audit ; aucune comparaison ne s'appuie dessus. Le seul === 'owner' du projet porte sur chanaccess_grade_de(), qui est une autre fonction, sur l'échelle des salons.
Sans cette vérification, renommer les retours d'une fonction aurait pu casser des comparaisons silencieusement. Les clés techniques ne bougent pas : Tools/panel-account.php accepte toujours viewer|op|coadmin|admin|owner, et la colonne level reste un entier.
⚠⚠ Une collision assumée, pas ignorée
Ces cinq niveaux gouvernent l'accès aux pages du panel, pas des pouvoirs sur IRC. Un « Net-admin » du panel n'a aucun droit sur le réseau, et son « Admin » n'est pas l'admin (6) des grades. Le même mot désigne donc désormais deux choses.
Cela a été mesuré, dit, et SerVuS a tranché en connaissance de cause. Le code et le banc le portent en toutes lettres, pour que personne ne le redécouvre plus tard comme un défaut.
⚠ Un rang déduit, et signalé comme tel
LEVEL_COADMIN (60) reçoit « Chef-de-bot » parce qu'il tombe entre Admin et Opérateur. Ce n'est pas dicté, c'est déduit — le commentaire le dit, et la correction tient en un mot si l'intention était autre.
Modifications
Web/inc/auth.php—level_name()rend le vocabulaire.Web/tests/test-grades.php— 29 constats, 6 mutations toutes détectées. Les constats ne portent pas que sur les cinq valeurs rondes : ils gardent aussi les seuils (un>=changé en>ferait glisser chaque niveau d'un cran sans qu'aucun libellé ne change), l'ordre des rangs et leur unicité.
4.16.60 — 13 août 2026
Le correctif précédent annonçait un succès qui n'existait pas : il relisait avec l'outil dont il doutait.
Constaté au redémarrage suivant. desarmer_flood_join a été appelée, elle n'a rien journalisé — donc elle a rendu « réussi ». Et le ban est reparti, les cinq refus aussi, et IriX.chan, écrit par le bot lui-même, portait toujours flood-join 5:60 sur les cinq salons.
⚠⚠ Le défaut était dans la relecture, pas dans l'écriture
La procédure vérifiait son channel set par un channel get — la même commande, sur le même réglage. Si Eggdrop ne reconnaît pas ce réglage par cette voie, les deux se trompent ensemble, et la relecture confirme n'importe quoi.
C'est une relecture qui ne prouve rien, et elle avait toutes les apparences d'une bonne pratique : le projet exige de relire après écriture partout ailleurs, et c'est justifié — mais relire suppose une source indépendante. Ici la seule qui le soit est IriX.chan, que le bot réécrit à l'heure ronde, et elle disait non.
Ce que cette version fait, et ce qu'elle ne fait pas
Elle ne corrige pas le flood : elle rend la mesure lisible. La procédure journalise désormais, inconditionnellement, l'état lu avant l'écriture, celui lu après, et le motif du refus si channel set en a émis un. Ces trois-là, confrontés à IriX.chan, tranchent entre les deux hypothèses restantes — une syntaxe qu'Eggdrop n'accepte pas, ou un réglage écrasé après coup.
⚠ La trace est temporaire et le dit : une fois la cause connue, elle pourra redevenir conditionnelle.
⚠ Et un constat de ce banc était creux, lui aussi
Il vérifiait que le journal contenait le mot « avant ». Or ce mot est littéral dans le message : supprimer la lecture qu'il annonce laissait le banc vert. Le talon n'aidait pas — il rendait la même valeur aux deux lectures.
Corrigé des deux côtés : le talon distingue l'avant de l'après, et le constat cherche les valeurs, pas les libellés. Un libellé n'est pas une mesure, et c'est le troisième constat creux trouvé aujourd'hui par une passe de mutation.
Modifications
IriX/cmds/IriX-sql.tcl— trace inconditionnelle des deux lectures et du refus.IriX/tests/test-floodjoin.tcl— 22 constats, 12 mutations toutes détectées.
✅ Ce que la trace a donné, le soir même
flood-join sur #!Services! — avant : « 0 0 » · après : « 0 0 ». Le compteur était déjà désarmé avant l'appel : l'écriture de la version précédente avait bien pris, et IriX.chan le confirme — #!Services! porte 0:0 là où les quatre autres salons gardent 5:60. Plus aucun « Flood de JOIN » depuis.
Le ban revenait pour une autre raison : il est mémorisé dans le userfile, et Eggdrop le rejoue à chaque entrée sur le salon. Le fichier donne lui-même le motif et les dates :
- *!*@IriX.overtchat.fr:1786646157:+1786638957:1786640826:IriX:join flood
Posé à 18:35:57 — le dernier flood réel, avant le correctif — et expirant deux heures plus tard, ce qui est exactement le ban-time du salon.
⚠⚠ Deux conclusions hâtives sur le même point, en sens opposés. D'abord « aucune conséquence fonctionnelle », alors que le ban est réel. Puis, l'ayant vu revenir malgré le compteur désarmé, « flood-join n'était pas la cause » — faux aussi : elle l'était depuis le début. Ce qui manquait n'était pas la cause, c'était de savoir que le ban persiste au lieu d'être recréé.
Juger sur l'absence d'un message coûte un aller-retour complet. Ce qu'il fallait faire, et qui a tout donné en une fois : chercher où l'objet vit. Le userfile fait 729 octets et tout y était écrit, motif compris ; les recherches précédentes y cherchaient un motif au lieu de simplement l'ouvrir.
4.16.59 — 13 août 2026
Le bot bannissait le serveur des services de leur propre salon, à chaque démarrage.
Cinq refus revenaient du serveur à chaque montée du lien — « Cannot kick protected user » contre les cinq services. Le carnet les avait notés la veille comme du bruit, « sans conséquence fonctionnelle ». C'était faux.
Ce qui se passait vraiment
flood-join 5:60 compte cinq entrées du même hôte en une minute. L'injection des services en fait exactement cinq, en moins d'une seconde, toutes sous l'hôte du serveur de services. Eggdrop conclut au flood, et réagit comme il le ferait contre un vrai flood : il pose un ban sur *!*@<hôte des services>, puis tente d'expulser les cinq.
Le serveur refuse les kicks — les services sont protégés. Il ne refuse pas le ban. Le salon des services portait donc un ban contre le serveur des services, reposé à chaque démarrage : 27 floods et 25 refus au journal du jour, et le ban encore en place au moment du constat. Sans effet sur ceux qui sont déjà entrés, et fatal au premier qui devrait entrer ensuite.
⚠ Ce qui a fait chercher au mauvais endroit
Rien de tout cela ne vient du projet : le kick et le ban sont émis par Eggdrop lui-même. Le code ne porte aucun putkick sur ce chemin, et les quatre pistes d'abord suivies — les commandes manuelles, la pose de +O, la fermeture administrative, les bans persistants — étaient toutes fausses.
⚠ Et les réglages du salon ne le disaient pas non plus : les cinq salons portent exactement les mêmes. Ce qui distingue celui des services, ce n'est pas sa configuration, c'est que nous y injectons cinq clients d'un coup. Le compteur mesurait un flood que nous produisions nous-mêmes.
⚠⚠ Il a fallu les TROIS journaux, et chacun en portait un tiers
IriX-sql.log: les cinq refusNUMERIC 484— le symptôme ;IriX.log: leMODE #!Services! +b *!*@IriX.overtchat.fr— la conséquence qui compte, et que personne n'avait vue ;journalctl: « Flood de JOIN … ! Interdiction. » — la cause, et elle n'était que là.
Chercher dans un seul journal aurait donné trois enquêtes différentes, toutes incomplètes. C'est la règle du carnet, et elle vient de payer trois fois dans la même heure.
Le correctif
::soc::ulines::auto::desarmer_flood_join, appelée avant la boucle d'injection, sur les deux chemins — celui qui lit svc_config et le repli. Corriger un seul tient tant que la table est renseignée, et tombe le jour où l'on bascule sur le repli, c'est-à-dire le jour où l'on a d'autres soucis.
⚠ On désarme le compteur plutôt que d'espacer l'injection : les cinq services doivent partager le même horodatage de salon, sans quoi le serveur refuse leur op et diffuse un avis de désynchronisation à tous les opérateurs. C'est écrit dans join_svc_chan depuis longtemps ; les espacer aurait cassé cela pour réparer autre chose.
⚠ Relu après écriture : channel set peut refuser sans lever. Sans relecture, on annoncerait un compteur désarmé qui ne l'est pas — et le ban repartirait au démarrage suivant, en silence.
Créations
IriX/tests/test-floodjoin.tcl— 18 constats, 9 mutations toutes détectées.
4.16.58 — 13 août 2026
Effacer une mémoire d'absence demandait une preuve, et n'en demandait aucune.
Relecture critique du code écrit une heure plus tôt. non_servis rend une liste vide dans deux cas : « tous les prêts sont servis », et « ma requête a échoué sous son catch ». La suspension traitait les deux pareil — tout prêt hors de la liste était réputé servi, et son compteur d'absence remis à zéro.
Ce que ça coûtait : une erreur SQL passagère effaçait un compte de vingt-trois heures, sans un mot. Et si l'erreur se répétait — une table verrouillée, un lien qui tousse —, la suspension ne serait jamais partie pendant que le journal annonçait « rien à défaire ». Une confirmation mensongère de plus, sur le chemin même qu'on venait d'écrire pour en éviter une.
La règle qui en sort : effacer une mémoire demande une preuve positive, pas une absence de preuve. siege_vraiment est donc rappelée avant tout effacement — la même que celle qu'emploie non_servis, pour ne pas inventer un second verdict qui divergerait un jour.
Les deux derniers schémas initialisés au démarrage
::soc::botreg::db_init et ::soc::botnet::db_init rejoignent les huit autres dans start_system.
⚠ Cette correction est PRÉVENTIVE, et il faut le dire. Le schéma déclaré de leurs sept tables a été croisé avec la base : tout concorde, aucune colonne ne manque. Rien n'est réparé. Ce qui est fermé, c'est la porte par laquelle la prochaine colonne serait restée lettre morte — sans un mot, comme absent_depuis l'a été.
Un contrôle DÉRIVÉ, plutôt que trois constats écrits à la main
test-appels.tcl vérifie désormais que tout db_init défini est appelé dans start_system. Il les découvre dans les sources : un module neuf y entre tout seul, et il n'y a pas de liste à tenir à jour.
C'est le point important. Trois constats nommés auraient gardé les trois modules connus — or c'est très précisément une énumération à la main qui les avait oubliés. Le banc borne sa recherche au corps de start_system : le nom d'un db_init figure aussi dans la liste de do_init_database, et chercher dans tout le fichier l'aurait rendu vert pour la mauvaise raison, celle-là même qu'il interdit.
⚠⚠ Un constat devenu creux, et découvert par la mutation
Le constat qui gardait la garde du reflet vide comptait les log:WARN. Or non_servis en émet un pour sa propre garde : retirer celle de la suspension laissait donc le banc vert — il lisait le message du voisin.
⚠ Et il ne l'était pas encore la veille : avant la preuve positive, la mutation était détectée, mais pour une raison qui n'était pas la bonne. Un constat peut donc devenir creux sans que personne y touche, quand le code autour de lui change. Seule une passe de mutation relancée le montre.
Il mesure maintenant ce que la garde apporte et qu'elle seule apporte : on sort sans interroger la base, et le journal le dit avec ses mots à elle.
Modifications
IriX/cmds/irix-mandat.tcl— preuve positive avant tout effacement de mémoire.IriX/cmds/IriX-sql.tcl—botregetbotnetinitialisés au démarrage.IriX/tests/test-appels.tcl— le croisement dérivé desdb_init.IriX/tests/test-mandat.tcl— 195 constats ; avectest-appels.tcl, 17 mutations toutes détectées.
4.16.57 — 13 août 2026
La suspension des prêts était muette quand tout va bien — le défaut de la 4.16.53, refait le jour même.
Deux versions plus tôt, ce journal expliquait pourquoi un contrôle silencieux ne prouve rien : son appel horaire est enveloppé dans un catch, donc une erreur y serait avalée sans un mot, et le silence se lirait « aucun défaut » exactement comme « le contrôle ne tourne plus ». La suspension, écrite le même jour, recommençait exactement cela.
⚠ Et la trace du signalement ne la couvrait pas. Les deux appels ont leur propre catch et leur propre garde info commands : l'un peut parfaitement tourner pendant que l'autre est mort. Un contrôle ne prouve la vie que du code qui l'écrit.
⚠ Le défaut a été vu en préparant le constat, pas en relisant le code — la même méthode qu'en 4.16.53, et la même question : « comment saurai-je que cela fonctionne ? » Sur un réseau où tous les prêts sont servis, la réponse était : je ne le saurai pas.
⚠ Connaître un travers ne suffit donc pas à ne pas le refaire, même en l'ayant écrit soi-même quelques heures plus tôt. Ce qui l'attrape n'est pas la mémoire, c'est le geste : se demander ce qui prouverait que le code tourne, avant de le déclarer en service.
Modifications
IriX/cmds/irix-mandat.tcl— une trace de vie au niveau le plus bas, une fois par heure, quand il n'y a rien à défaire.IriX/tests/test-mandat.tcl— 192 constats, 14 mutations toutes détectées ; retirer la trace fait rougir le banc.
4.16.56 — 13 août 2026
Le schéma des prêts ne s'initialisait qu'à la main, et aucune migration ne pouvait donc l'atteindre.
La version précédente ajoutait une colonne. Elle a été écrite, éprouvée, déployée, le bot redémarré — et la base ne l'a jamais vue. Le défaut n'était pas dans la migration : il était dans ce qui aurait dû l'exécuter.
::soc::start_system appelle sept initialisations de schéma au démarrage. Celle du mandat n'en faisait pas partie : elle n'était atteinte que par la commande d'administration init_database, que personne ne lance sur un réseau qui tourne. Le schéma d'origine avait donc été posé une fois, à la main — et tout ce qu'on lui ajouterait ensuite serait resté lettre morte.
⚠⚠ Et il serait resté muet
C'est ce qui rend ce défaut coûteux. CREATE TABLE IF NOT EXISTS ne se plaint de rien sur une table déjà là ; un ALTER qu'on n'atteint jamais ne dit rien non plus. Aucun journal, aucune erreur, aucun banc rouge : la migration était juste, son banc était vert, et elle ne s'exécutait pas. Seule une mesure en base l'a montrée — SHOW COLUMNS après le redémarrage.
⚠ Le premier réflexe était d'accuser la migration elle-même, puis le redémarrage, puis la garde db_column_exists. Aucun des trois n'était en cause. Ce qui a tranché : chercher qui appelle db_init, plutôt que relire ce qu'elle fait. C'est la même méthode que pour les cinq portes de l'authentification — chercher qui ÉCRIT, pas qui semble écrire.
⚠ Deux autres modules sont dans le même cas
::soc::botreg::db_init et ::soc::botnet::db_init ne sont pas davantage appelés au démarrage. Leurs schémas tiennent aujourd'hui parce qu'ils ont été posés à la main, et toute colonne qu'on leur ajouterait subirait exactement ce qui vient d'arriver. Ce n'est pas corrigé ici : le constat est porté au carnet plutôt que traité en passant, un module à la fois et chacun avec son constat.
Modifications
IriX/cmds/IriX-sql.tcl—::soc::mandat::db_initappelée dansstart_system, aprèsload_config. ⚠ Jamais au niveau du fichier : un appel posé ausources'exécute avant la configuration, et c'est le défaut qui a produit 171 erreurs SQL à chaque démarrage depuis le 27/07/2026.IriX/tests/test-mandat.tcl— 191 constats, 13 mutations toutes détectées.
⚠ Deux constats creux trouvés dans le banc du jour
Le premier employait string match sur un motif portant des crochets : il les lit comme une classe de caractères, et le constat rougissait sur un code juste. Le second comparait deux positions de string first sans exiger qu'elles soient trouvées — un appel absent rend -1, et « -1 est avant start_system » est vrai : le constat serait passé au vert précisément dans le cas qu'il interdit. C'est le piège des rangs de lsearch que le carnet nomme, retrouvé sur une autre commande.
4.16.55 — 13 août 2026
Un prêt que personne ne sert finit par se suspendre — après 24 heures, et seulement après.
Le lot de la veille disait, et ne faisait rien d'autre. C'était un choix, et il portait un argument : un salon peut devenir réservé par mégarde, et son propriétaire corriger le mode dans l'heure ; défaire aussitôt serait punir une configuration passagère. L'exploitant a renversé ce choix — l'argument n'est pas écarté pour autant, il vit désormais dans le délai. Ce qui survit à vingt-quatre avertissements horaires n'est plus une configuration passagère.
⚠⚠ La colonne à laquelle il ne fallait surtout pas se fier
La table des prêts porte déjà updated_at, et le premier réflexe était de s'en servir : elle est là, elle est datée, elle vieillit toute seule. Elle mesure l'âge de la LIGNE, jamais celui du défaut.
Mesuré avant d'écrire une ligne de code : le prêt du salon d'accueil — parfaitement servi, robot présent, aucun écart signalé — portait 1582 minutes. Un seuil de vingt-quatre heures posé dessus aurait suspendu sur-le-champ un salon devenu inaccessible dix minutes plus tôt, et épargné un salon mort depuis trois jours parce que quelqu'un venait d'y toucher.
Une colonne absent_depuis porte donc le premier constat d'absence, et rien d'autre. Elle s'ouvre au premier passage qui ne voit pas le robot, s'efface dès qu'il revient, et c'est elle seule qu'on chronomètre.
⚠ Écrire cette mémoire sans casser l'avertissement qui l'accompagne
updated_at porte ON UPDATE CURRENT_TIMESTAMP. Marquer un prêt absent rajeunirait donc sa ligne — et le contrôle qui l'a signalé, qui écarte les prêts touchés depuis moins de quinze minutes, cesserait de le voir. Le marquage aurait effacé l'avertissement qu'il accompagne. L'écriture réaffecte donc explicitement updated_at = updated_at, et un constat le garde.
La garde du reflet vide, cette fois sur le chemin qui ÉCRIT
Sur le signalement, elle empêche de crier au loup. Ici elle empêche bien pire : si la table d'observation est vide, ce ne sont pas les robots qui sont partis, c'est nous qui ne voyons plus rien — et une suspension naïve marquerait absents tous les prêts du réseau, puis les défferait tous le lendemain.
Elle est sortie en une procédure unique, appelée par les deux chemins. Deux copies d'une même garde, c'est la copie qu'on oublie de poser le jour où l'on ajoute un troisième chemin — le travers exact des trois compteurs de charge, qui ne comptaient chacun que la moitié de la même chose.
Suspendre, jamais terminer
Terminer est sans retour : le code le dit lui-même, « il ne se rétablit pas, il se réaccorde ». Un prêt suspendu rentre d'un seul MANDATON <#salon> le jour où le salon redevient accessible. Le droit accordé par un opérateur n'est pas détruit, il est mis en pause — et l'annonce faite aux opérateurs dit comment le rétablir, sans quoi elle serait un constat de plus.
⚠ La mémoire d'absence s'efface au moment de suspendre. L'oublier rendrait la réactivation inopérante : le prêt rétabli retomberait suspendu au passage suivant, sa vieille date encore en place, sans que personne comprenne pourquoi.
⚠ Ce que la passe de mutation a trouvé
Douze mutations, et une a survécu : le constat sur la casse ne portait que sur un sens. La casse arrivait déjà rabattue d'un côté, si bien que retirer le traitement de l'autre côté laissait le banc vert. Un seul sens ne garde qu'une moitié de porte — la leçon exacte des pseudonymes protégés, où un banc à sens unique aurait laissé passer un correctif qui cassait les robots. Les deux sens sont désormais éprouvés, et les douze mutations détectées.
⚠ Et le piège Tcl repayé une quatrième fois
Un constat comparait $::BASCULES à {{#membres suspendu}} et rougissait sur un code juste : une liste dont le premier élément commence par # se représente {#membres} suspendu. Le carnet nomme ce piège, et deux bancs qui l'annonçaient y étaient déjà tombés. La parade n'est pas de le connaître : c'est de ne plus jamais comparer une liste autrement que par lindex ou llength.
Créations
::soc::mandat::suspendre_non_servis, appelée au passage horaire après le signalement — l'ordre compte : suspendre d'abord sortirait le prêt des actifs, et le dernier avertissement ne partirait jamais.::soc::mandat::_absence_ecrire, qui n'accepte que les deux littéraux SQL utiles — ce qui n'est pas prévu se refuse, il n'a pas à s'échapper.::soc::mandat::_observation_vivante, la garde du reflet vide, partagée par les deux chemins.- Colonne
absent_depuissur la table des prêts, posée par unALTERdédié : unCREATE TABLE IF NOT EXISTSne s'exécute pas sur une table déjà là, et la colonne n'aurait existé que sur les installations neuves.
Modifications
IriX/tests/test-mandat.tcl— 189 constats, dont 32 neufs, et 12 mutations toutes détectées.
4.16.54 — 12 août 2026
« Il n'y est pas » était faux : la procédure regardait quelqu'un d'autre.
Le contrôle des prêts, écrit une heure plus tôt, a tourné pour la première fois à 16:00:00 — et il a annoncé aux opérateurs du réseau que le salon d'accueil était sans modérateur. Le robot y était. Son propre état, poussé à la minute près, annonçait ce salon.
D'où venait le mensonge
La table qui dit qui siège où porte une ligne par couple salon/robot, pas une par salon. Sur le salon d'accueil, IriX siège — comme sur tous les salons du réseau — et le robot prêté siège : deux lignes.
La procédure lisait « la première ligne de ce salon », sans filtrer sur le robot qu'on lui demandait. Elle rendait donc IriX, le comparait au robot prêté, et concluait qu'il n'était pas là.
⚠ Le piège est qu'elle PARAISSAIT juste. Elle lisait la bonne table, remplie par observation et non par déclaration, et son commentaire annonçait fièrement « c'est de l'observé ». Elle l'était. Elle observait simplement quelqu'un d'autre.
⚠ Le défaut est plus ancien que le contrôle qui l'a révélé
L'autre appelant de cette procédure affiche, dans la fiche d'un prêt, si le robot est présent ou non. Il affichait donc déjà « le robot n'est PAS sur le salon » à tort, pour la même raison — sur tout salon où IriX siège en plus du robot, c'est-à-dire sur tous. Il fallait simplement qu'un salon ait deux occupants et que quelqu'un regarde.
⚠ Ce qu'aucun banc ne pouvait donner
Cette procédure n'était éprouvée nulle part : les bancs la remplaçaient par un bouchon, et son seul autre appelant se contente d'afficher du texte. Elle est désormais couverte, le cas à deux occupants compris.
⚠ Et le premier bouchon écrit pour elle était infidèle : il comparait en tenant compte de la casse, là où la base l'ignore. Le banc rougissait sur un code juste. Un bouchon plus strict que la base accuse à tort — c'est le piège « SQL contre code », retrouvé cette fois dans le banc et non dans le code.
Septième mutation ajoutée : retirer le filtre remet le défaut, et le banc rougit.
4.16.53 — 12 août 2026
Le contrôle des prêts dit maintenant qu'il est passé, même quand tout va bien.
Le lot précédent, écrit une heure plus tôt, était muet dans le cas sain — et son appel horaire est enveloppé dans un catch. Une erreur y aurait donc été avalée sans un mot, et le silence du contrôle se serait lu « aucun prêt en défaut » exactement comme « le contrôle ne tourne plus ».
C'est le travers que ce projet paie depuis le début, déjà rencontré ailleurs : rien au journal ne prouve rien. La preuve doit être différentielle. Une ligne de trace par passage, au niveau le plus bas, suffit à distinguer les deux — et elle ne dérange personne, les opérateurs n'étant prévenus que d'un vrai défaut.
⚠ Le défaut a été vu en préparant le constat, pas en relisant le code : en cherchant comment prouver que le mécanisme tournait, il est apparu qu'aucun moyen n'existait. Se demander « comment saurai-je que cela fonctionne ? » est souvent ce qui trouve le trou.
Sixième mutation ajoutée au banc — retirer la trace le fait rougir. Les six sont détectées.
4.16.52 — 12 août 2026
Un prêt accordé que personne ne sert se dit maintenant aux opérateurs.
C'est le point 11, ouvert le matin même : rien ne vérifiait qu'un robot pouvait entrer dans le salon qu'on lui confie. Le prêt s'écrivait, l'ordre partait, le robot répondait « c'est fait » — et le salon restait sans modérateur, sans que personne le sache.
Constater, plutôt que refuser à l'avance
Refuser à l'octroi demandait de lire les modes du salon. La table qui les porte n'est pas fiable : deux salons sur cinq y portaient une valeur qui n'est pas une chaîne de modes. Une décision fondée dessus se serait trompée sans qu'on sache pourquoi. Et un salon peut devenir restreint après l'octroi : un contrôle à l'avance ne l'attraperait jamais.
Observer le résultat couvre au contraire tous les empêchements d'un coup — mode réservé, chiffrement exigé, clé, invitation, limite atteinte, bannissement, robot muet — sans en énumérer aucun. C'est la règle de ce projet : le déclaré et l'observé, jamais l'un à la place de l'autre.
⚠⚠ La garde qui compte plus que le reste
Si l'on n'observe plus rien nulle part, on ne signale rien. Une table d'observation vidée ferait déclarer tous les prêts non servis et annoncerait un désastre sur un réseau parfaitement sain. C'est le piège déjà payé ailleurs : quand plus personne n'est vu, c'est l'observation qu'il faut regarder, pas les robots.
Et un délai de grâce, qui n'est pas un confort : un prêt tout juste accordé n'a pas encore été observé — le robot entre en une seconde, le constat vient au cycle suivant. Sans lui, chaque octroi serait annoncé comme un échec.
⚠ Ce que ce lot ne fait PAS, délibérément
Il ne défait pas le prêt. Un droit accordé par un opérateur ne se retire pas tout seul : un salon peut être restreint par mégarde, et son propriétaire corriger cela dans l'heure. Défaire serait punir une configuration passagère. Le manque n'était pas la sanction, c'était le silence.
Créations
::soc::mandat::non_serviset::soc::mandat::signaler_non_servis, appelées au passage horaire après la bascule de charge — l'ordre compte : signaler avant elle dénoncerait un prêt qu'elle s'apprête à corriger.
⚠ Ce que la passe de mutation a trouvé
Le banc comptait 18 constats neufs, tous verts. Cinq mutations plus tard, l'un d'eux s'est révélé creux : il vérifiait que la procédure était mentionnée dans le fichier — et son nom y figure aussi dans un commentaire et dans une garde. Débrancher l'appel laissait le banc vert.
C'est le travers « mentionnée, pas appelée » que ce projet paie depuis le début, et aucune relecture ne l'aurait montré. Le constat exige désormais l'appel lui-même. Les cinq mutations sont détectées.
4.16.51 — 12 août 2026
Un opérateur du réseau n'est pas expulsé par les bans automatiques — constaté dans les deux sens.
C'était la dernière moitié d'un point ouvert depuis le 11 août, et le carnet la disait hors de portée : elle ne se mesure pas en base, elle se lit dans un client. Un client d'essai la lit très bien.
Un ban visant un seul pseudonyme — celui du compte de vérification, aucun visiteur possible — est posé sur un salon d'administration, puis ce compte y entre deux fois, une par état de son grade.
| Son grade | Résultat |
|---|---|
| au-dessus du seuil | épargné, aucune expulsion |
| aucun grade | expulsé, motif « Ban persistant » à l'appui |
⚠⚠ Pourquoi la seconde passe compte plus que la première
« Je n'ai pas été expulsé » se lit aussi bien l'exemption tient que le ban ne mord pas. Un essai qui ne mesure que le cas favorable ne prouve rien du tout — il produit une confirmation qui a l'air d'une preuve. La seconde passe est ce qui transforme l'une en l'autre.
⚠⚠ Et elle montre en direct ce que le carnet annonçait sans l'avoir vu
Le serveur a envoyé la même notice dans les deux cas — que le statut d'opérateur dispense du ban — et le robot a expulsé quand même la seconde fois. L'exemption du serveur dispense de la barrière, pas du coup de pied, qui ne demande l'avis de personne. Deux gardes à deux étages, et c'est celle du robot qui décide.
⚠ Ce n'est donc pas le statut d'opérateur du serveur qui protège, mais un grade inscrit en base et authentifié. Un opérateur du réseau sans cette ligne est expulsé comme un visiteur — la seconde passe le montre, le compte étant resté opérateur du serveur d'un bout à l'autre.
Le journal du robot corrobore le client d'essai à la seconde près, ce qui fait deux sources indépendantes pour un même constat.
Nettoyage
Table des bans revenue à zéro, mode retiré du salon, authentification désarmée, et le corpus du modèle de modération toujours à zéro — l'essai ne lui a rien fait apprendre, ce qui n'allait pas de soi sur un salon où le filtre est armé.
4.16.50 — 12 août 2026
La bascule de charge a basculé pour de vrai — et le salon n'a pas été servi.
Un robot arrêté à 13:11, le seuil de trente minutes franchi, un passage horaire. À 14:00:00, la reprise part toute seule :
bascule : #membres repris par OverOp-H (à la place de OverOp-S, absent)
bascule : 1 repris, 0 sans repreneur, 0 refus
Le prêt change de porteur, l'ordre d'entrée part, le robot en accuse réception sept secondes plus tard, et les opérateurs du réseau sont prévenus par une annonce nominative. C'est ce que le mécanisme promettait, et il le fait.
Le choix du repreneur était le bon, pour une raison qui ne se voyait pas
Une prévision faite une minute plus tôt annonçait un autre robot. Celui qui a repris paraissait alors le plus chargé — mais le prêt qu'il portait est arrivé à échéance à la même seconde, le ramenant à zéro. À égalité, l'ordre alphabétique tranche. La charge se mesure au moment où la bascule décide, jamais avant.
⚠⚠ Et pourtant le salon est resté sans personne
Une minute après, le contrôle d'écart le dit :
écart d'état pour OverOp-H — absent de : #membres
Le salon est réservé aux pseudonymes identifiés. Les robots ne le sont auprès d'aucun service — le serveur refuse donc leur entrée, et il le fait exprès : cette restriction-là est une promesse faite aux membres, que même un opérateur du réseau ne peut pas outrepasser. Aucun des quatre robots ne pouvait servir ce salon.
⚠ Le robot l'a dit lui-même, au moment du nettoyage — et c'est la confirmation la plus nette qu'on pouvait avoir : « je n'étais pas sur ce salon ».
Ce que ce constat découvre
Rien ne vérifie qu'un robot pourra entrer dans le salon qu'on lui confie. Ni à l'octroi, ni à la bascule. Le prêt paraît vivant en base et au panel, le salon n'a aucun modérateur, et le seul signal est une ligne d'écart toutes les cinq minutes.
⚠ Le code le disait déjà. Le commentaire de la procédure d'entrée prévient qu'elle rend « ok » quand l'ordre est parti, jamais quand le robot est entré. C'est exact et honnête — mais aucun appelant ne regarde ensuite. Une mise en garde que personne ne lit ne garde rien.
Le défaut est inscrit au point 11 de Markdown/CHANTIERS.md, avec les trois pistes possibles et ce qui les sépare.
✅ La cause est isolée — contre-épreuve du même jour, 15:28
Le salon d'accueil du réseau a retrouvé un robot, et le même ordre, envoyé au même robot, a produit cette fois :
15:28:14 ordre o1786541294-4 : #accueil activé, entrée en cours
15:28:15 OverOp-H joined #accueil.
Une seconde. La chaîne d'ordre n'a donc jamais été en cause : c'est bien la restriction du salon précédent qui barrait l'entrée. Sans cette contre-épreuve, « le robot n'entre pas » aurait pu s'imputer au protocole, au robot ou à la bascule — trois pistes fausses, et du temps perdu sur chacune.
⚠ Et le journal des écarts le dit en différentiel, ce qui vaut mieux qu'un silence : trois lignes d'erreur, une par cycle, tant que le prêt désignait un salon inaccessible — puis plus une seule depuis qu'il est servi. Le contrôle fonctionne, il avait raison, et il se tait quand tout concorde.
⚠ Ce prêt-ci est SANS TERME, contrairement au précédent qui durait un jour et s'est évaporé à son échéance sans que personne le remarque. Le salon d'accueil du réseau n'est pas un prêt d'essai.
⚠ Avertissements
- Le statut du point 2 de
Markdown/CHANTIERS.mdattend l'arbitrage de l'exploitant : la bascule a tenu toutes ses promesses, mais le salon n'a pas été servi. Les deux lectures sont écrites côte à côte dans le carnet plutôt que tranchées d'autorité. - L'essai a été nettoyé par le chemin normal — le prêt terminé, l'ordre de sortie émis, le salon désactivé chez le robot, et le tout relu en base plutôt que supposé.
4.16.49 — 12 août 2026
Un motif de protection qui visait à côté depuis le premier jour.
La liste des pseudonymes réservés du réseau portait over-*, censé garder le préfixe « Over » pour les services. Le motif exige un tiret juste après over — il ne correspondait donc à aucun des noms que les services portent vraiment. La protection avait l'air posée et ne gardait rien.
Comment il s'est vu
Non pas en relisant le fichier, où la ligne se lit très bien, mais en croisant ses motifs avec les pseudonymes réellement présents sur le réseau. Le croisement appelle le match() du serveur lui-même : réécrire la comparaison aurait produit un second glob, et deux globs finissent par diverger.
Un seul nom sur dix était couvert, et il l'était par une autre ligne.
Ce qui change
Les pseudonymes des services sont désormais nommés un par un. Le motif over-* est conservé — il garde bien les formes à tiret — mais il n'est plus tenu pour suffisant, et le fichier explique pourquoi.
⚠ Pourquoi pas over*, le correctif évident
Il serait plus large et plus mauvais. D'abord il refuserait Overlord, Overdose ou Overwatch à des visiteurs ordinaires, ce contre quoi le fichier met lui-même en garde. Ensuite et surtout, les robots de prêt sont des clients : ils ouvrent une connexion comme n'importe qui et se verraient refuser leur propre nom. Ils ne tomberaient même pas franchement — Eggdrop bascule alors sur son pseudonyme de repli et continue de tourner, le prêt cassé sans une ligne au journal.
Créations
Servers/overtchat-hybrid/tests/test-nickpolicy.c— septième campagne du serveur. Elle n'éprouve pas un mécanisme mais le fichier qu'on livre, et les deux ne se remplacent pas : le mécanisme fonctionnait parfaitement le jour où la mesure a montré que presque rien n'était gardé.
Elle garde les deux sens — que les services soient réservés, et que les robots ne le soient jamais. 16 constats, et deux mutations qui la font rougir : retirer un service de la liste, et élargir le motif en over*.
Modifications
Servers/overtchat-hybrid/conf/pseudos-interdits.example— quatre pseudonymes de services ajoutés, et la raison écrite sur place ;Servers/overtchat-hybrid/tests/run-tests.sh— la campagne y est inscrite le jour où elle est écrite. Le lanceur énumère à la main : une campagne qui n'y figure pas ne tourne jamais, et laisse croire que le sujet est couvert.
✅ Et la campagne entière a été relancée pour le vérifier : les dix-sept passent, la nouvelle comprise, à sa place et comptée par le lanceur. Éprouver un banc à la main prouve qu'il fonctionne, pas qu'il tournera — et c'est la seconde qui protège.
✅ CONSTATÉ EN SERVICE le 12/08/2026 à 14:26
Le report en production a été suivi d'un rechargement à chaud — le serveur n'a pas changé de processus, donc pas une connexion rompue. Il annonce 84 motifs au lieu de 80, et le croisement, refait sur le fichier réellement chargé, ne laisse plus à découvert que les cinq pseudonymes qui relèvent du mot de passe.
Puis les trois cas ont été éprouvés sur le serveur en service, par de vraies connexions :
OverBot → 432 :Erroneous Nickname: Ce pseudonyme est reserve aux services du reseau
OverOp-Q → 433 (deja pris), et non 432 — le robot garde son nom
Overlord → accepte
⚠ Le troisième cas compte autant que le premier. Sans lui, un refus général se lirait comme un succès ; et sans le deuxième, on ne saurait pas qu'un robot peut encore se connecter.
⚠ Recoupé par le journal du serveur, qui est une source indépendante du client d'essai — et il ne porte qu'une ligne, pour le seul cas qui devait être refusé :
14:26:25 Pseudonyme « OverBot » refusé (liste des interdits) pour 127.0.0.1
⚠ Avertissements
- Ce lot ne couvre que les pseudo-clients du lien de services. Les robots et IriX se connectent en clients : les inscrire dans cette liste leur interdirait de se connecter. Leur protection passe par
pseudos-proteges.conf, qui demande un mot de passe par pseudonyme — le mécanisme est écrit et éprouvé, mais les mots de passe ne se posent pas automatiquement. VoirMarkdown/CHANTIERS.md. - Le fichier de politique vit hors du dépôt, dans la configuration du serveur en service. Mettre à jour le modèle ne met pas à jour une installation : il faut y reporter le changement, puis recharger.
4.16.48 — 12 août 2026
La bascule de charge — point 2 du carnet, ouvert depuis le 09/08.
Quand un robot passe hors ligne, les salons qu'il servait sont repris par un autre qui a des places. Sans cela, un prêt survit à son robot : la base annonce un modérateur, le salon n'en a aucun.
⚠ Ce qu'il a fallu attendre pour l'écrire
Le carnet l'interdisait tant que les écarts n'étaient pas lisibles — « une bascule qui répartit d'après un compte faux est pire que pas de bascule ». Trois conditions, toutes remplies ce jour :
- un prêt réel existe depuis le 11/08, donc
BOTSTATa de quoi comparer ; - les deux compteurs de charge sont croisés par un banc (4.16.40), et leur divergence sur la casse corrigée ;
- 136
BOTSTATreçus dans la journée, zéro écart journalisé.
⚠⚠ La garde qui compte plus que le reste
${t}_online est vidée par un HUP et a 30 s de retard. Au cycle suivant, tous les robots paraissent absents — et une bascule naïve les déplacerait tous, en même temps, sur personne.
Si aucun robot n'est joignable, rien n'est basculé. Quand tout le monde est absent, ce n'est pas eux, c'est nous. Même règle que le balayage des pseudonymes, qui refuse de supprimer quand il ne sait pas.
Les autres gardes
| Garde | Ce qu'elle empêche |
|---|---|
Le robot doit être absent depuis 30 min, état et seen_at | qu'un netsplit de trois minutes déclenche une reprise |
| Passage horaire, jamais plus fréquent | qu'une bascule nerveuse suive chaque hoquet du réseau |
Le repreneur doit être enabled, online, vu récemment, et avoir de la place | qu'on confie un salon à un absent ou à un robot déjà plein |
| L'écriture d'abord, l'ordre ensuite | qu'un robot entre sur un salon que la base attribue encore à un autre — deux autorités, et personne pour trancher |
| La base est relue après l'écriture | que le mode strict avale la bascule en silence |
| Le robot tombé n'est jamais choisi comme repreneur | qu'il se reprenne lui-même |
Aucun PART n'est envoyé à l'ancien | qu'on donne un ordre à un fantôme |
| « Aucun repreneur » est dit, pas contourné | qu'on entasse des salons sur un robot plein « faute de mieux » |
⚠ À égalité de charge, le premier par ordre alphabétique. Un départage au hasard rendrait deux passages successifs incomparables, donc un incident indéboguable.
⚠ Les opérateurs sont prévenus par annonce_opers : une bascule change qui modère un salon, et c'est le genre de fait qu'on découvre trop tard si personne ne l'annonce.
Erreurs
⚠⚠ ::soc::mandat::basculer EXISTAIT DÉJÀ, avec la signature {chan etat} — c'est elle qui suspend ou réactive un prêt, et IriX-sql.tcl l'appelle. Ma procédure portait le même nom : en Tcl, la seconde définition écrase la première sans un mot, et la commande de suspension aurait cessé de fonctionner sans que rien ne le signale.
Trouvé par le banc, au tout premier lancement, avant tout déploiement. Renommée bascule_charge. C'est le pendant du travers déjà nommé dans le carnet : on y guette la procédure qui n'existe pas, celle qui existe DÉJÀ coûte tout autant. Vérifier qu'un nom est libre avant de l'écrire — grep "proc <nom>" suffit.
Évolutions
IriX/tests/test-bascule.tcl— 32 constats, dont le premier est la garde du HUP. ⚠ Son fauxdb_selfiltre comme la base le fait : l'exclusion du robot tombé vit dans une clause SQL, et un décor qui rend tout le parc ne pourrait pas l'éprouver.- 61 bancs au vert, 3547 constats.
- 9 mutations, toutes détectées — dont celle qui vide la garde du HUP.
⚠ Une mutation a survécu au premier essai : le constat cherchait le nom de la procédure dans le balayage horaire, or ce nom figure aussi dans la garde info commands. Il cherche désormais l'appel.
Avertissements
⚠ Écrite et éprouvée, PAS ENCORE CONSTATÉE EN RÉEL. Il y faut un robot réellement tombé pendant plus de 30 minutes, puis un passage horaire. Tant que ce n'est pas fait, ce point reste ouvert dans CHANTIERS.md — « écrit et éprouvé » n'est pas « en service », c'est la règle du carnet.
4.16.47 — 12 août 2026
Les cahiers rattrapent la journée. Six documents relus contre ce qui a changé — et trois portaient des affirmations devenues fausses.
Modifications
ROADMAP.md— ⚠ le point le plus faux du lot. Il annonçait encore « l'unité reste à installer », « la phrase secrète n'existe pas » et « aucune sauvegarde n'a jamais été produite sur cette installation ». Les trois étaient vrais le 11/08 au matin, et faux le lendemain : unité installée et active, minuteur rattrapé tout seul au démarrage, phrase secrète ouvrant les quatre archives, archive restaurée pour de vrai, et copie hors machine depuis la 4.16.44. Ne reste que la recopie de la phrase hors de la machine.IRIX.md—!banpermajouté au tableau des commandes, avec ses trois gardes et le refus de toute durée.DATABASE.md— nouvelle section sur la casse, à côté de celle qui traitait déjà l'accent-insensibilité : c'est le même piège une lettre plus loin. La collation_cicompare sans la casse, le code avec — et la règle qui en découle : une garde d'existence quiSELECTla colonne doit adopter ce qu'elle lit, pas seulement tester sa vacuité.SECURITY.md— les deux incidents de secret du 12/08, dont aucun ne venait d'un secret écrit volontairement : une vieille copie oubliée sur un partage réseau, et un filtre bâti sur le nom de la clé (set pass…) là où la directive s'appelledbpass. Plus la chaîne de renouvellement du mot de passe de la base, et les deux fichiers qui le portent.EDITIONS.md,PRET-ROBOT.md— les comptes de bancs écrits à la main sont retirés et renvoient au lanceur. Un chiffre à la main ne survit pas à la séance qui l'écrit ; celui-ci datait de trois séances.
Évolutions
CHANTIERS.md: six points revus dans la journée, trois fermés par constat — le webchat (3), le message d'accueil (6) et l'exemption des opérateurs (8).- 60 bancs au vert, 3515 constats.
4.16.46 — 12 août 2026
La rotation distante — du code qui supprime, sur le disque de quelqu'un d'autre.
Sans elle, les archives s'accumulaient sur la Freebox à raison de 4,4 Mo par jour. Avec elle, on efface — et le disque porte les photos, les vidéos et une copie du projet. Une suppression mal visée y ferait des dégâts qu'aucune sauvegarde ne rattraperait, puisque c'est la sauvegarde qui les causerait.
Créations
- La rotation distante, dans
copier_hors_machine, pour les deux voies — un chemin monté comme le FTP. Réglable pargarder=dans la configuration.
⚠ Le distant garde 30 par défaut, le local 7. C'est voulu : le distant est la copie de survie, sur un disque qui ne sert qu'à ça ; le local partage sa place avec le projet lui-même.
⚠⚠ Ce qu'elle ne supprimera jamais
| Garde | Ce qu'elle protège |
|---|---|
Le motif overtchat-*.tar.gz.gpg, et rien d'autre | Photos, vidéos, la copie du projet — tout ce qui n'est pas à nous |
-type f et -maxdepth 1 | Les répertoires, et tout ce qui est en dessous |
| Listage impossible → on ne touche à rien | L'ignorance ne vaut pas autorisation. Même règle que le balayage des pseudonymes |
| Aucune archive reconnue → on ne touche à rien | Un dossier qui ne contient pas nos archives n'est pas le nôtre |
| L'archive du jour est écartée de la liste | Celle qu'on vient de déposer ne peut pas être la victime |
⚠ Deux défauts trouvés par le banc, et le second par la mutation
1. Le tri dépendait de la locale. sort suit la langue du système et ignore la casse : « overtchat-essai… » passait avant « overtchat-Maverick… », la plus récente se retrouvait en tête, et le saut qui la protège consommait un tour de boucle — une archive de trop survivait, et l'ordre changeait d'une machine à l'autre. Corrigé par LC_ALL=C sort.
2. L'archive du jour était sautée dans la boucle, au lieu d'être écartée de la liste. Sautée, elle faussait le compte de ce qu'on supprime. Elle est maintenant retirée avant.
⚠ Et la mutation a montré que le banc ne pouvait pas voir cette garde-là. Avec les vrais noms (overtchat-Maverick-AAAAMMJJ-…), la plus récente trie toujours en dernier : l'écarter ou non ne change rien, et un décor bâti sur ces noms ne prouve rien. Il a fallu un décor où elle trie en premier — le seul cas où la garde décide.
Évolutions
Web/tests/test-backup.php: 85 → 103 constats.- 60 bancs au vert, 3515 constats.
- 13 mutations, toutes détectées.
⚠ Trois mutations ont été retirées, et on dit pourquoi plutôt que de les laisser passer pour « survécues » — ce qui ferait croire le banc aveugle :
- le tri sans
LC_ALL=Cn'est pas détectable de façon portable ; le résultat dépendrait de la langue de la machine qui lance le banc ; - les deux gardes de l'ignorance se recouvrent, et le compte est figé avant elles : trois verrous imbriqués qu'aucune mutation simple ne franchit ;
-type f: sans lui,findliste le répertoire, maisrm -fsans-rrefuse de l'effacer. La garde reste — un répertoire dans la liste consomme un tour de boucle — mais elle n'est pas observable par une destruction.
4.16.45 — 12 août 2026
tls_verifier — le certificat de la box, et pourquoi ce choix se déclare.
Le FTP de la Freebox refuse la copie en curl 60 : elle présente un certificat qu'aucune autorité connue ne signe, et elle ne publie pas la sienne (ca.crt → 404 sur mafreebox.freebox.fr comme sur l'IP, mesuré).
Accepter ce certificat sans le vérifier protège de l'écoute passive, pas d'un intercepteur actif. C'est défendable vers une IP fixe de sa propre box, sur son propre réseau — et indéfendable ailleurs. Le réglage est donc explicite, jamais implicite :
tls_verifier=non
⚠ Le défaut VÉRIFIE, et une valeur inattendue ne désactive rien : seul le mot non ouvre la porte. Le transport reste chiffré dans les deux cas (--ssl est conservé) — c'est la seule vérification du pair qui tombe.
Évolutions
Web/tests/test-backup.php: 79 → 85 constats, dont les trois qui gardent ce réglage : le défaut vérifie,nonajoute-kaux deux appels, et une valeur inconnue ne l'ajoute pas.- 60 bancs au vert, 3495 constats.
Avertissements
⚠ Le montage du disque a été essayé d'abord, et abandonné sur mesure. Le noyau WSL2 ne connaît que 9p : mount -t drvfs sur un UNC échoue, cifs est absent, un lecteur réseau Windows (F:) reste invisible depuis WSL et depuis tout processus hors session — y compris par l'interop PowerShell, donc y compris sous systemd, qui est justement le contexte de la sauvegarde. Le FTP est la seule voie qui tienne sans intervention à chaque démarrage.
4.16.44 — 12 août 2026
La sauvegarde quitte enfin la machine.
⚠⚠ Ce que la mesure a montré
Le carnet réclamait depuis le 11/08 que la phrase secrète soit recopiée hors de la machine. En vérifiant, un point plus large est apparu : les archives, la phrase et le projet vivaient tous les trois sur le même disque (/dev/sdd), et aucune copie n'existait nulle part ailleurs — 0 archive dans OneDrive, aucun autre support monté.
Une sauvegarde qui ne quitte pas la machine protège d'une erreur — un rm malheureux, une migration ratée. Elle ne protège pas d'une panne de disque, d'un vol, ni d'un rançongiciel qui chiffrerait les archives avec le reste.
✅ La phrase, elle, a été vérifiée au passage : elle ouvre les quatre archives présentes, constaté sans jamais l'afficher.
Créations
copier_hors_machinedansTools/backup-overtchat.sh— appelée après la production ET la vérification de l'archive locale : copier une archive illisible ne servirait à rien.
Deux voies, et la plus simple d'abord :
| Voie | Ce qu'il faut |
|---|---|
chemin=/mnt/z/… | Le disque connecté en lecteur réseau côté Windows — ce que BACKUP.md recommande déjà. Aucun identifiant n'a à vivre dans un fichier |
url=ftp://… | Rien à installer : curl est déjà là. Identifiants dans un fichier 600, hors dépôt |
Si les deux sont donnés, le chemin gagne : moins de pièces mobiles, et aucun secret à manipuler.
⚠ Ce qu'elle refuse de faire
- Le répertoire absent n'est PAS créé. Disque débranché ou lecteur non reconnecté après un redémarrage de Windows : l'écrire quand même le poserait sur le disque local, et l'on croirait avoir une copie ailleurs. C'est la pire des issues, et elle est explicitement fermée.
- Un échec de copie ne fait pas échouer la sauvegarde. L'archive locale est déjà produite et vérifiée : un disque débranché ne doit pas faire rendre « échec » à une sauvegarde valide.
- Une copie tronquée est détectée par la taille, des deux côtés — un code de retour à zéro ne prouve jamais qu'un fichier est arrivé entier. Même règle que pour l'archive locale.
- Le mot de passe ne passe jamais dans l'URL : une URL se retrouve dans les journaux du serveur distant, là où une option de ligne de commande ne va pas. Et il n'apparaît dans aucun message, pas même dans ceux d'erreur — c'est le serveur qui avait recopié un secret dans sa réponse le 09/08/2026, sans qu'on le lui ait demandé.
- Sans configuration, rien ne part — et elle le dit. Une copie qu'on croit active et qui ne tourne pas est pire que pas de copie.
Évolutions
Web/tests/test-backup.php: 42 → 79 constats, avec un fauxcurlet uncptruqué : on exécute la fonction, on ne la relit pas. C'est le seul moyen de savoir ce qui partirait vraiment sur le réseau — et surtout ce qui ne partirait pas.- 60 bancs au vert, 3491 constats.
- 10 mutations, toutes détectées, dont celle qui crée le répertoire absent et celle qui retire l'appel depuis
do_backup.
⚠ Deux mutations ont survécu au premier essai, et les deux pour la même raison : le banc cherchait la présence d'un motif là où il y a deux appels curl — l'envoi et la relecture de la taille. Muter le premier laissait le second intact. Il compte désormais. Et la troisième survivante a révélé un trou franc : aucun constat n'éprouvait une copie tronquée par la voie chemin, un vrai cp copiant toujours tout. D'où le cp truqué.
4.16.43 — 12 août 2026
Le défaut annoncé n'existait pas — et le constat en réel l'a montré après que je l'eus « corrigé ». Un point du registre invalidé, un doublon supprimé, et un vrai défaut trouvé en chemin.
⚠⚠ Ce que le registre annonçait, et pourquoi c'était faux
Depuis le 11/08, le carnet portait : « le serveur ajoute l'horodatage aux bans du burst ; record_mode le prendra pour un paramètre de mode et l'affichera dans l'onglet Activité ». La 4.16.41 a ajouté _sans_ts dans record_mode pour l'empêcher.
L'essai en réel a dit non, pour deux raisons indépendantes.
Un ban a été posé sur #accueil, IriX redémarré, et le serveur a bien envoyé ce qu'il devait :
BURST: :10A MODE #accueil +b *!*@banc-essai-ts.invalid 1786521110
1786521110 est l'horodatage de création d'#accueil. Et pourtant IriX_events n'a rien reçu — pas de ligne du tout.
- Le dispatcher ne voit JAMAIS le burst, et le code le dit lui-même (
IriX-sql.tcl, l. 1217) : « cette boucle lit le burst en entier avant de passer en LINKED, si bien que nitrack_burstni le dispatcher ne le voient jamais ».on_modeest branché au dispatcher : un MODE du burst ne l'atteint pas. - Et
on_moderetirait DÉJÀ l'horodatage, depuis l'origine, pour les modes reçus après le lien. Son commentaire s'en expliquait même : le retrait vivait là « plutôt que dansrecord_mode, que le chemin client emprunte aussi ».
J'avais donc ajouté un second retrait à l'endroit que le code déconseillait, contre un défaut qui ne pouvait pas se produire.
⚠ Le premier essai n'avait rien prouvé non plus, et pour une troisième raison. Le ban avait d'abord été posé sur #!Services! — dont IriX et les cinq pseudo-clients sont les seuls membres. Au redémarrage du bot, le salon meurt et se recrée : son TS est passé de 1786521126 à 1786524348, et sa liste de bans est partie avec. Le burst annonçait « 0 ban(s) ». Un salon qu'aucun tiers n'occupe ne survit pas au redémarrage du bot — #accueil si, parce qu'OverOp-H y siège.
Bugs corrigés
⚠ Un vrai défaut a été trouvé en consolidant, et lui existait bel et bien. Le test qui vivait dans on_mode coupait au format seul — « dernier mot, entier d'au moins neuf chiffres ». Une limite de salon en est un :
MODE #chan +l 1786521110 → la limite disparaissait du journal
Absurde comme limite, parfaitement valide comme mode. _sans_ts ne coupe que ce qui est en surplus de ce que les modes consomment : +l ayant consommé son paramètre, il n'y a pas de surplus, et la limite reste.
Évolutions
- Un seul retrait de l'horodatage, au point de passage unique (
record_mode). Il y en avait deux depuis le matin — et deux copies d'un même calcul divergent, c'est la règle de ce carnet. IriX/tests/test-modtrace.tcléprouve désormais le chemin S2S de bout en bout (on_mode), dont le cas+l 1786521110qui était faux.- 60 bancs au vert, 3456 constats. 6 mutations sur
modtrace, toutes détectées.
Erreurs
⚠ Le registre a porté ce point pendant un jour sans qu'il soit mesuré. Il était vraisemblable — le serveur envoie bien l'horodatage, record_mode recopie bien ce qui suit les modes — et faux, parce que les deux ne se rencontrent jamais. Une chaîne dont chaque maillon est vrai n'est pas une chaîne qui existe.
⚠ Et j'ai écrit le correctif avant l'essai. Le banc était vert, les mutations détectées : rien de tout cela ne pouvait dire que le chemin n'était pas emprunté. Seul l'essai en réel le pouvait.
4.16.42 — 12 août 2026
!banperm — le ban permanent, sur tout le réseau. Demandé par SerVuS le jour même.
Créations
::soc::ban::perm(IriX-sql.tcl), liée à!banpermenpubet exposée sur la console DCC sous.banperm.
Deux points tranchés par SerVuS :
- réservé au grade
admin— soitadmin,owneretircops. Le grade Admin/Owner d'Eggdrop reste l'autorité principale : c'est le statut de celui qui a fait lehello; - posé immédiatement sur tous les salons enregistrés, ET inscrit en
channel='*'— les deux, pour qu'il survive aux redémarrages et vaille sur les salons à venir.
⚠ channel='*' n'est pas une convention nouvelle : ::soc::ban::delete la lit déjà, et la page des réglages de salon annonce que « les bans du réseau (channel = '*') s'appliquent aussi ».
⚠ Aucune durée n'est acceptée, et c'est le sens du mot : expires reste NULL. Offrir un !banperm … 2h serait un contresens, et une seconde façon de poser un ban temporaire.
Trois gardes, chacune contre un accident précis
| Garde | Ce qu'elle empêche |
|---|---|
Grade admin | qu'un op de salon ferme le réseau |
| Masque complet exigé | !newban devine l'hôte depuis le salon courant — absurde quand la portée est le réseau, et un ban permanent se poserait sur une adresse choisie au hasard |
*!*@* refusé | permanent et posé partout, il bannirait le bot, les cinq services et les opérateurs — et la commande qui le défait n'arriverait plus |
⚠ L'écriture d'abord, la pose ensuite. Si la base refuse, aucun mode n'est envoyé : un ban visible sur douze salons mais absent de la base disparaîtrait au premier redémarrage, sans que personne sache pourquoi.
⚠ Ce qui n'a pas pu être fait se dit. Les salons suspendus et ceux où le bot ne siège pas sont sautés, et comptés dans la réponse. Annoncer « posé partout » alors que trois salons n'ont rien reçu ferait croire à une protection qui n'existe pas.
⚠ La console ne réécrit rien : .banperm passe par le pont pont_pub existant, donc par la même garde require_grade. Une seule règle d'accès, quel que soit le chemin d'entrée.
Évolutions
IriX/tests/test-banperm.tcl— 32 constats, dont le croisement des binds : le banc vérifie que la commande est réellement liée, enpubcomme en console. Quatre fois sur ce projet, la procédure était juste et personne ne l'appelait.- 60 bancs au vert, 3451 constats.
- 9 mutations, toutes détectées.
✅ Et un banc existant a fait son travail sans qu'on le lui demande. test-grades.php a rougi sur « aucune commande liée n'est passée sous silence » : la commande était liée, le site ne la documentait pas. Une commande ouverte qu'aucune page n'annonce est précisément ce que ce banc existe pour empêcher. Déclarée dans Web/inc/commandes.php — et le banc a dérivé du code le grade exigé pour le comparer à celui de la page, sans qu'on ait à le lui dire.
Erreurs
⚠⚠ Une CINQUIÈME issue de mutation a été trouvée, et elle mentait comme les quatre autres : « MUTÉE AILLEURS ». Le carnet en connaissait quatre — survécue, détectée, plantée, non appliquée. Deux mutations ont d'abord été annoncées « SURVÉCUE » : le motif if {![string match "*!*@*" $mask]} existe aussi dans ::soc::ban::add, cent lignes plus haut, et c'est elle qui était mutée. Le fichier changeait bien — le garde-fou « le fichier n'a pas changé » ne voyait donc rien — et le script concluait « le banc ne détecte pas », alors que la procédure visée n'avait pas été touchée.
Vérifier qu'un fichier a changé ne suffit pas : il faut vérifier que la BONNE zone a changé. La mutation est désormais bornée à la portée de la procédure éprouvée.
Avertissements
⚠ La nomenclature des grades reste incohérente dans le projet, et ce point n'est pas ouvert par cette version — il est seulement constaté. Quatre vocabulaires coexistent : require_grade (user…ircops), overtchat-setup.sh (owner/admin/coadmin/xop et op/halfop/voice), les colonnes (cs*), et celui de l'exploitant (Net-admin, Chef-de-bot, Helpeur…). coadmin n'existe dans rien de ce qui décide : ni colonne, ni lecture, ni écriture. L'ajouter à la seule liste require_grade en ferait un « niveau fantôme » — le défaut documenté au 20/07/2026 pour halfop et csadmin.
4.16.41 — 12 août 2026
Deux heures d'écart et un nombre de dix chiffres. Les deux derniers points que le carnet gardait ouverts sans qu'ils demandent une décision.
⚠ L'avis de travaux se datait en UTC
Tools/travaux.php annonçait « ouvert : 08:17:49 » pour un avis posé à 10h17. Deux heures d'écart sur la seule information qui dise depuis quand il traîne — et cet outil sert précisément les jours où l'on ne se fie plus à rien.
⚠ Le site, lui, était juste. PHP retombe sur UTC (ini date.timezone vaut "UTC" alors qu'aucun php.ini ne déclare rien), et c'est identity.php qui pose le fuseau du système. Le site le charge ; la ligne de commande, non.
⚠ La correction n'est pas un require de config.php. L'en-tête de Tools/travaux.php l'écarte explicitement : il porte des secrets et peut manquer sur une machine fraîche. identity.php est versionné, n'a aucune dépendance et ne lit que le disque — il tient la promesse « ni base, ni session ». Le fuseau y est lu dans le système, jamais écrit en dur : ce logiciel s'installe ailleurs que chez son auteur.
Posé dans Web/inc/travaux.php, à côté du repli de e() qui existait déjà pour exactement la même raison — un module appelé des deux côtés.
⚠ L'horodatage du burst s'affichait comme un paramètre de mode
Depuis la 4.16.34, burst_channel émet chaque masque suivi de l'horodatage du salon (s_serv.c) :
:<SID> MODE <#salon> +b <masque> <creationtime>
Il le faut : sans lui, Anope prenait le masque pour un horodatage, levait « Invalid timestamp » et fermait le lien, emportant celui d'IriX. Mais IriX reçoit le même burst, et record_mode recopiait tout ce qui suit les modes. L'onglet Activité aurait affiché +b *!*@x 1786521126.
⚠ L'appariement n'a jamais été faux — _apparier consomme un paramètre par mode et ignore le surplus, donc target restait juste. C'était un défaut d'affichage, et il ne fallait surtout pas « corriger » la cible en passant.
Trois gardes, pour qu'un paramètre légitime ne soit jamais mangé : le surplus doit valoir exactement un, avoir la forme d'un horodatage Unix, et un mode qui consomme un nombre l'a déjà consommé — +l 1786521126 reste une limite intacte, absurde mais légitime.
⚠ raw_line garde la trame entière. Le filtre ne touche qu'à l'affichage : sans cela, il deviendrait indébogable le jour où il se trompe.
Bugs corrigés
Web/inc/travaux.php— chargeidentity.php, qui pose le fuseau du système. L'avis se date à l'heure locale en ligne de commande comme sur le site.IriX/cmds/irix-modtrace.tcl—_sans_tsretire l'horodatage du burst de l'affichage des modes.
Créations
IriX/tests/test-modtrace.tcl— 27 constats. Le carnet notait depuis le 11/08 qu'aucun banc Tcl ne couvraitmodtrace, alors que c'est lui qui remplit l'onglet Activité.
Évolutions
_ciblesest désormais bâtie sur_apparier, qui rend l'appariement et le nombre de paramètres consommés. Compter dans une seconde procédure aurait fait deux lectures des mêmes trois tables de modes — et la seule question aurait été de savoir quand elles divergeraient.- 59 bancs au vert, 3419 constats (58 et 3392 avant).
- 6 mutations sur
modtrace, toutes détectées — dont celle qui retire l'appel à_sans_tssans toucher à la procédure. C'est le trou à la jointure, payé quatre fois sur le chantier des robots.
4.16.40 — 12 août 2026
Sur IRC, #Accueil est #accueil — le robot le savait, la base aussi, et le bot non. Un défaut trouvé en écrivant le banc qui devait seulement vérifier que deux compteurs étaient d'accord.
⚠ Le défaut, et ce qu'il coûtait
« La charge d'un robot » se calcule à deux endroits indépendants : le bot (::soc::botreg::salons_attendus, deux requêtes et un lsearch) et le site (botreg_charge_sql, deux sous-requêtes et un NOT IN). Les deux réunissent les mêmes sources depuis le 11/08 — un robot sert un salon par enregistrement ou par prêt — et dédoublonnent, puisqu'un salon peut être cité par les deux.
Mais ils ne dédoublonnaient pas de la même façon :
| Comparaison | Effet | |
|---|---|---|
| Site | NOT IN, soumis à la collation utf8mb4_uca1400_ai_ci | ignore la casse |
| Bot | lsearch -exact | distingue la casse |
Or #Accueil et #accueil sont un seul salon : le serveur ne les distingue pas, et le robot annonce toujours la casse que le serveur lui donne. Deux conséquences, la seconde plus coûteuse que la première :
- le même salon prenait deux places du parc pour le bot, une seule pour le site — deux définitions de « la charge », ce que le module refuse par écrit ;
- l'écart entre ce que la base attribue et ce que le robot sert se signalait à chaque
BOTSTAT, pour un robot parfaitement à sa place : une ligneerrortoutes les cinq minutes, 288 par jour de prêt.
C'est le défaut du 11/08 — salons_attendus ne lisant qu'une source — retrouvé par une autre porte, sur la même table et avec le même coût.
⚠⚠ Et la porte était ouverte des DEUX côtés, par le même geste
botreq_deposer (site) et ::soc::mandat::accorder (bot) vérifient tous deux que le salon existe :
SELECT channel FROM `…_channels` WHERE channel = '<saisi>'
La comparaison étant insensible à la casse, « #Accueil » passe pour « #accueil ». Les deux requêtes lisent donc le nom canonique — et les deux le jetaient, écrivant la casse saisie. La bonne valeur était sous la main, dans une variable déjà remplie.
Bugs corrigés
irix-botreg.tcl—salons_attendusetstat_ecartcomparent désormais les noms de salon sans égard à la casse (lsearch -exact -nocase), aux quatre endroits concernés.chargessuit sans y toucher : il compte parsalons_attendus, et le dit.irix-mandat.tcl—accorderadopte la casse de la base (set chan $connu) avant d'écrire le mandat.Web/inc/botreq.php—botreq_deposeradopte la casse de la base avant d'écrire la demande.
Créations
Web/tests/test-charge.php— le banc que le carnet réclamait depuis le 11/08 : il croise les deux implémentations de la charge. Ingrédients (les deux tables,etat = 'actif', le dédoublonnage), casse, et chemins d'écriture — plus un croisement de comportement sur les données réelles, recoupant le SQL avec un compte direct fait en PHP.
⚠ Il mesure la collation au lieu de la supposer : le jour où elle deviendrait sensible à la casse, c'est lui qui le dirait le premier.
⚠ Et il annonce ce que les données du jour ne permettent pas de distinguer — sans recouvrement entre les deux tables, le croisement n'éprouve pas le dédoublonnage, et un vert s'y lirait comme un satisfecit.
IriX/tests/test-mandat-accorder.tcl— 17 constats sur la procédure qui écrit le prêt. Elle n'était gardée par rien :test-mandat.tclcompte 116 constats et la remplace par un faux pour observer son appelant. Le trou n'était pas à la jointure cette fois — il était dans la procédure.
Évolutions
- 58 bancs au vert, 3392 constats (56 et 3346 avant).
- 20 mutations passées sur les trois corrections, toutes détectées. ⚠ L'une d'elles a survécu au premier essai : le banc cherchait la présence d'un
-nocaselà où il en fallait deux, un par source. Il compte désormais. C'est la mutation qui a trouvé la faiblesse, pas la relecture.
Avertissements
- ⚠ Les lignes déjà écrites ne sont pas réparées. La correction empêche d'en créer de nouvelles et rattrape la comparaison ; elle ne réécrit aucune ligne existante. Au 12/08 aucune ne diverge —
botreq,mandatetopmybotportent toutes la casse canonique. - ⚠ Le chemin du site n'est éprouvé que statiquement.
botreq_deposerécrit en base : le banc vérifie que la correction est en place, pas qu'elle s'exécute. Le pendant côté bot, lui, est éprouvé sur décor.
4.16.39 — 11 août 2026
La sauvegarde existe, se restaure — et la restauration a trouvé qu'elle oubliait le serveur du projet.
Le point le plus grave du registre est fermé, et par constat :
| Archive | 4,4 Mo, PGP symmetric — AES 256, salted & iterated, SHA512 |
| Base | dump de 1,2 Mo, 58 tables — le compte exact |
| Restauration éprouvée | 567 fichiers, 14 Mo, dans un répertoire temporaire |
⚠ Le script vérifie lui-même son archive (« intégrité vérifiée ») — cela ne dit pas qu'on saura la rendre. Elle a donc été restaurée pour de vrai, et son contenu recoupé pièce par pièce. C'est la seule preuve qui compte.
⚠⚠ Et c'est la restauration qui a trouvé le trou
Servers/overtchat-hybrid/ était absent de l'archive — 31 414 lignes de C, le serveur écrit pour ce projet. Le motif --exclude='*/Servers' visait les 130 Mo de serveurs tiers décompressés (inspircd 55 Mo, hybrid 36 Mo, ircu 21 Mo…), tous retéléchargeables. Il emportait au passage les 1,2 Mo du seul écrit ici.
⚠ Ce n'était pas critique — le code est sur le dépôt distant — mais une archive dont la restauration exige un accès réseau n'est pas un filet. C'est très exactement le principe que le script énonce dix lignes plus haut : « ce qui n'existe nulle part ailleurs ne s'exclut pas, si petit soit-il ».
Créations
_exclusions_serveurs— les serveurs tiers sont désormais dérivés de ce que git ne suit pas, jamais énumérés. ⚠ Une liste écrite à la main aurait laissé passer le prochain serveur tiers ajouté : c'est le travers que ce projet paie le plus souvent. Repli sur l'exclusion large si git est indisponible — mieux vaut une archive amputée qu'une archive de 130 Mo.
Modifications
Web/tests/test-backup.php— 46 constats. ⚠ Le banc exécute la dérivation sur un décor fabriqué (un dépôt git temporaire avec un serveur suivi et un non suivi) au lieu de relire le bloc statique : depuis cette version, les motifs quetarreçoit ne sont plus tous écrits dans le script.Markdown/BACKUP.md— les userfiles des robots, trou qui n'était signalé nulle part.
⚠ Ce que l'archive ne prend pas, et pourquoi
| Absent | Raison |
|---|---|
/home/servus/overtchatd/ | hors du dossier archivé — déjà documenté |
/home/servus/overop-{h,q,s,t}/ | les userfiles des robots — trou trouvé le 11/08, signalé nulle part. Sans eux, Eggdrop repart en création de userfile et refuse systemd : il faut redonner le hello à chacun |
~/.overtchat-oper-* | volontaire — une archive qui porterait les secrets ferait d'elle-même la cible |
~/.config/overtchat/backup.pass | volontaire, et surtout : la phrase qui déchiffre l'archive n'a rien à y faire |
Erreurs
⚠ Mon banc a crié sur du code juste, et il avait raison de crier. En lisant le bloc EXCLUDES=(…) statique, il ne voyait plus les exclusions ajoutées à l'exécution — donc moins de motifs que tar n'en recevra. Il accusait le script de ne plus exclure Servers/ircu, qu'il exclut pourtant. Il comparait le code à une image périmée de lui-même.
⚠ Et mon premier correctif de banc ne prouvait rien : il passait $R, qui est un préfixe fictif (Service-Overtchat) et non un chemin, puis le vrai clone — dont git ne répond pas à travers un montage Windows. Dans les deux cas la dérivation rendait le vide, et les constats passaient sans rien mesurer. D'où le décor fabriqué.
4.16.38 — 11 août 2026
Le chantier des quatre éditions est préparé — et il ne doit pas s'ouvrir maintenant.
Demandé par SerVuS « pour quand tout sera stable » : découper le projet en Beta (découverte), Standard (novice), Complète et Payante, plus deux chantiers greffés — le budget et l'hébergement de l'édition payante, et une compatibilité Windows avec un installateur qui règle tout selon le mode choisi.
Créations
Markdown/EDITIONS.md— l'inventaire mesuré, les quatre éditions détaillées, cinq questions à trancher avant d'écrire une ligne, la licence, l'hébergement, le portage Windows.
Ce que la mesure a donné
⚠ Les pourcentages du cahier — « 60 % », « 40 % » — n'avaient rien à quoi se rapporter. Ils en ont maintenant : le serveur pèse 31 414 lignes, IriX 26 524, le site et le panel 34 033, l'installateur 10 429 à lui seul (291 fonctions, 88 entrées de menu). 58 tables, 12 unités systemd.
✅ Aucun problème de licence tierce, et c'est mesuré : Anope, Eggdrop et UnrealIRCd sont téléchargés chez leurs éditeurs à l'installation — git ls-files en compte zéro. Et le serveur IRC est écrit de zéro, non un fork d'ircd-hybrid : son README le dit et l'explique.
Avertissements
⚠⚠ Le point le plus dur n'est pas technique. Le projet est sous licence MIT : « vous pouvez le copier, le modifier, le redistribuer et vous en servir, y compris commercialement ». N'importe qui peut donc retirer une limitation et redistribuer. Une édition payante fondée sur du MIT vend un service — hébergement, mises à jour, support —, pas un logiciel. Les deux autres voies (changer de licence, garder un dépôt fermé) sont décrites, avec ce qu'elles coûtent.
⚠ Windows : l'obstacle est l'installateur, pas le serveur. Le serveur emploie epoll avec un repli poll(2) — la boucle d'événements est déjà derrière une abstraction, donc le portage est ciblé sur event.c et la couche socket. Les 10 429 lignes de bash de l'installateur, elles, seraient à réécrire entièrement.
⚠ Une édition, c'est aussi une campagne de bancs. Aujourd'hui : 56 bancs, 3 342 constats, sur une configuration. Sans une campagne par édition, trois sur quatre casseront sans que personne le voie — personne ne les installe tous les jours.
⚠⚠ Et le chantier ne doit pas démarrer tant que les huit points du registre sont ouverts, dont la sauvegarde qui n'a jamais produit d'archive. Un projet qu'on découpe en quatre multiplie par quatre ce qu'il a de bancal. Les défauts trouvés aujourd'hui ne se sont vus qu'en faisant tourner le projet ; quatre éditions, ce sont quatre choses à faire tourner.
4.16.37 — 11 août 2026
Un SQUIT ne nous concerne que s'il nous désigne. Et le coupable n'était pas celui que j'avais nommé.
La branche SQUIT du dispatcher ULine se déconnectait sur tout SQUIT passant sur le lien, sans jamais regarder sa cible :
SQUIT {
putlog "SOC ULINES AUTO: \[$label\] SQUIT: [join $args]"
::soc::ulines::auto::disconnect_socket $id $label "SQUIT reçu"
}
⚠ Le raccourci a tenu des mois parce qu'IriX était le seul ULine du réseau : un SQUIT arrivant sur ce lien était forcément le sien. Le jour où Anope s'est lié au même serveur, chacun de ses départs a fait tomber IriX. Un défaut qui ne peut apparaître qu'à partir du deuxième service, et qu'aucune relecture n'aurait montré : le code était juste pour le réseau qu'il avait sous les yeux.
⚠⚠ J'avais accusé le serveur, et c'était faux
La version précédente a consigné, au registre et ici, que « le serveur propage le SQUIT d'un ULine à l'autre », avec une piste vers src/cmd/ et une recompilation en perspective. La mesure a dit l'inverse. Le journal d'IriX montre qu'il émet lui-même son SQUIT :
17:26:38 RAW OUT: SQUIT IriX.overtchat.fr :SQUIT reçu
17:26:38 [Overtchat] déconnecté — SQUIT reçu
Le serveur faisait son travail : un netsplit se relaie à tous les liens, c'est ainsi qu'ils l'apprennent.
Ce qui m'a égaré, c'est une ligne vraie. « Rupture du lien avec IriX.overtchat.fr : SQUIT reçu » vient bien du serveur — elle décrit ce qu'il fait, pas qui le lui a demandé. Lire les deux journaux avant de désigner un coupable, surtout quand le second est celui de son propre code.
Créations
::soc::ulines::auto::squit_nous_vise— compare la cible à notre nom de serveur et à notre SID. ⚠ Les deux : certains serveurs SQUITent par nom, d'autres par identifiant, et n'en reconnaître qu'une forme laisserait IriX accroché à un lien que le serveur vient de couper. ⚠ Comparaison insensible à la casse, et par égalité — un nom qui contient le nôtre n'est pas le nôtre.IriX/tests/test-squit.tcl— 12 constats, 4 mutations, dont celle qui remet l'ancien code.
Avertissements
⚠ On TIENT quand le SQUIT ne nous vise pas, et le risque est assumé. Si le serveur nous coupait vraiment, il fermerait aussi le socket et l'eof s'en chargerait. Se déconnecter par excès de prudence, en revanche, coupe un lien qui allait très bien — c'est le défaut qu'on vient de fermer.
✅ Constaté en réel
Le 11/08/2026 à 17h43 : Anope monté, lié (17 clients), puis arrêté.
:005 SQUIT services.overtchat.fr :Services terminating via signal Terminated (15)
[Overtchat] SQUIT sur « services.overtchat.fr » — ce n'est pas nous, le lien tient
Zéro netsplit d'IriX, son lien inchangé de part et d'autre, les cinq services toujours là et le prêt de #accueil intact — le scénario même qui coûtait un netsplit une heure plus tôt.
4.16.36 — 11 août 2026
L'installateur pose le SID d'Anope tout seul — la dernière des quatre valeurs qu'il fabriquait au lieu de les lire.
Le bloc link du serveur en porte quatre, et les quatre sont obligatoires : le nom, le port, le mot de passe et le SID. Chacune manquante donne un refus qui ne dit pas pourquoi, et il a fallu trois démarrages successifs pour les trouver une par une le 11/08/2026.
⚠ Le modèle d'Anope livre l'identifiant COMMENTÉ, avec une valeur d'exemple — #id = "00A". Il ne s'agit donc pas de changer une valeur mais de décommenter la ligne : un s|^id| aurait laissé le modèle intact sans rien signaler. Le motif accepte le # de tête, et la relecture exige la ligne décommentée — un grep sur la seule valeur l'aurait trouvée dans le commentaire, et aurait annoncé une configuration réussie.
Un bloc link sans sid ne fait pas échouer la configuration : tous les serveurs n'en exigent pas. Le cas est dit, et la ligne du modèle laissée telle quelle.
Créations
_anope_link_sid— ancré sur le nom du bloc, comme le mot de passe. ⚠ Le bloc d'IriX en réserve un autre et vient en premier dans le fichier : prendre « le premiersidvenu » donnerait à Anope l'identifiant du bot, c'est-à-dire deux serveurs pour un même SID.
Modifications
Bootstrap/tests/test-anope-lien.sh— 35 constats, 5 mutations. ⚠ La commandesedest extraite du script, jamais recopiée dans le banc : une copie aurait divergé le jour où l'originale change, et le banc aurait continué d'éprouver l'ancienne.
Avertissements
⚠⚠ Un SQUIT d'un ULine emporte le lien de l'AUTRE ULine, et c'est systématique. Un arrêt propre d'Anope fait tomber IriX, qui revient seul en une vingtaine de secondes. Ce n'est pas un accident d'essai : c'est ce qui a rendu les tentatives d'Anope si coûteuses, puisque Restart=on-failure le relançait toutes les dix secondes et que chaque tour emportait IriX.
⚠ CETTE VERSION A ACCUSÉ LE SERVEUR — À TORT. La cause était dans IriX, et le correctif est en 4.16.37 : la branche
SQUITdu dispatcher se déconnectait sur tout SQUIT passant sur le lien, sans regarder sa cible. Le serveur, lui, faisait son travail. Ce paragraphe est conservé tel qu'il a été écrit ; le suivant dit ce qu'il en était.
⚠ Anope est arrêté et disabled, à la demande de SerVuS : il sert à éprouver l'installation, la configuration et le lancement — pas à tourner. Le réseau reste servi par OverNick, OverTchat, OverBot, OverHub et OverWeb.
4.16.35 — 11 août 2026
Le panel disait « non déclaré » de sept services qui tournaient, et affichait ses heures avec deux heures de retard. Deux défauts signalés par SerVuS dans la demi-heure qui a suivi le premier lien d'Anope — deux choses qu'aucun banc ne regardait, et qu'aucune relecture n'aurait montrées.
1. Le panel ne suivait pas les include
La branche 2.1 d'Anope ne déclare aucun module de service dans anope.conf : chacun vit dans son fichier, tiré par un bloc include { type = "file" name = "nickserv.example.conf" }. Le panel lisait le seul fichier de tête, n'y trouvait rien, et concluait « non déclaré » — pour NickServ, ChanServ, OperServ, MemoServ, BotServ, HostServ et Global, tous en ligne à ce moment-là.
⚠⚠ Et suivre un include demande une garde qu'on n'a pas le droit d'oublier : Anope sait inclure la SORTIE D'UNE COMMANDE. Son propre modèle en donne l'exemple, avec un wget vers un domaine externe. Le panel lit une configuration — il n'exécute rien, et cette lecture ne devait jamais devenir le chemin par lequel un fichier ferait tourner une commande. La liste des types acceptés est donc blanche : tout ce qui n'est pas file est refusé, y compris un type qu'une version future inventerait.
Trois autres gardes, chacune contre un abus précis : un nom d'include reste un nom de fichier (ni /, ni .., ni chemin absolu) ; un fichier déjà lu n'est pas relu, sinon deux fichiers qui s'incluent l'un l'autre boucleraient ; et le plafond de lignes porte sur le total, sans quoi vingt fichiers sous le plafond le franchiraient ensemble.
2. PHP tournait en UTC
Le panel annonçait « dernière synchronisation 14:33 » à 16:33.
⚠ Plus grave que l'affichage : MariaDB suit l'horloge du système (time_zone = SYSTEM). PHP en UTC et la base en CEST, toute comparaison de dates entre les deux se trompait de deux heures — une expiration, une péremption, un « il y a combien de temps ». C'est la raison pour laquelle botreg_robots() fait calculer son TIMESTAMPDIFF par la base : le contournement était en place depuis longtemps, la cause ne l'était pas.
⚠ Le fuseau est LU dans le système, jamais écrit en dur : ce logiciel s'installe ailleurs que chez son auteur.
Créations
anope_conf_lignes_de()— la lecture récursive, avec ses quatre gardes.
Modifications
Web/inc/identity.php— pose le fuseau du système, avant toute date.Web/tests/test-anope.php— 96 constats, 3 mutations.Web/tests/test-json.php— le fuseau de PHP contre celui du système, et la dérive des deux horloges.
Erreurs
⚠⚠ ini_get('date.timezone') NE DIT PAS si quelqu'un a choisi. Un premier jet s'y fiait pour ne pas écraser un réglage d'exploitant — et ne changeait donc rien du tout : la fonction rend "UTC", la valeur compilée dans PHP, alors qu'aucun php.ini de cette machine ne déclare quoi que ce soit. Mesuré par grep, après avoir constaté que la correction n'avait aucun effet. Le piège est le même que celui de date_default_timezone_get(), que le commentaire dénonçait deux lignes plus bas.
⚠ Et deux constats neufs ont échoué sur du code juste, pour la même raison qu'hier : un motif trop large. Ils cherchaient le mot dehors dans les lignes rendues — or ../dehors.conf, le nom même de l'include refusé, contient ce mot. On cherche désormais name = "dehors".
⚠ Un constat dont la cible n'existe pas ne prouve rien. Le premier jet visait ../suite.conf, un chemin qui ne menait à aucun fichier : la lecture le sautait faute de cible, garde ou pas, et le constat restait vert sur un code sans protection. La mutation qui retirait les deux gardes de chemin a donc survécu. Le décor pose maintenant une vraie cible un cran au-dessus.
4.16.34 — 11 août 2026
Le burst des bans n'envoyait pas l'horodatage du salon — et c'est ce qui fermait le lien vers Anope.
Le serveur émettait chaque ban du burst ainsi, depuis son propre SID :
sendto_one(to, ":%s MODE %s +%c %s", conf.me.sid, chan->name,
lists[i].letter, ban->banstr);
Or quand la source d'un MODE de salon est un serveur, UnrealIRCd place l'horodatage du salon en dernier paramètre, et le module unrealircd d'Anope le lit sans condition :
auto final_is_ts = server_ts && source.GetServer() != NULL;
auto ts = final_is_ts ? IRCD->ExtractTimestamp(params.back()) : 0;
Anope prenait donc le masque pour un horodatage, levait Invalid timestamp: *!*@IriX.overtchat.fr et fermait le lien — en emportant celui d'IriX au passage, par le SQUIT qui suivait.
⚠ Le burst entier passait. Anope recevait les serveurs, les opérateurs, les salons et leurs membres : c'est la toute dernière ligne d'un salon qui le faisait tomber. Un lien qui monte n'est pas un lien qui tient.
Modifications
Servers/overtchat-hybrid/src/s_serv.c—burst_channelajoutechan->creationtime, le même horodatage que leSJOINquelques lignes plus haut. ⚠ Celui du SALON, pas celui du ban : c'est l'horodatage du salon qui arbitre les modes ; la date de pose ne dit rien à l'autre bout.
Avertissements
⚠ IriX reçoit le même burst, et son record_mode prend tout ce qui suit la cible comme paramètres de mode. L'appariement reste juste — _cibles consomme un paramètre par mode et ignore le surplus —, mais l'horodatage apparaîtra dans le texte journalisé d'un +b reçu au burst. À constater à la prochaine reconnexion d'IriX, et à corriger si l'onglet Activité en souffre. Aucun banc Tcl ne couvre modtrace.
⚠ Les bans serveur ne survivent pas à un redémarrage, et rien ne les retire au SQUIT. Mesuré en lisant ban.c : aucune persistance disque, et ban_remove n'est appelée que par un retrait manuel d'opérateur. Un service qui tombe laisse donc ses Q-lines jusqu'au prochain redémarrage du serveur — quatre l'ont été ici (OperServ, MemoServ, HostServ, Global), posées douze fois chacune par un Anope qui bouclait sur Restart=on-failure. Elles sont parties avec ce redémarrage. Le défaut de fond reste ouvert : sur un réseau qui ne redémarre pas, elles resteraient pour toujours.
4.16.33 — 11 août 2026
Le prêt de robot va d'un bout à l'autre — constaté. Et le port d'Anope était fabriqué, une heure après avoir corrigé le nom et le mot de passe.
✅ oper_si_prete — le dernier maillon du prêt de robot
Constaté en service le 11/08/2026, sur #accueil, après que l'exploitant a retiré l'op au robot prêté :
16:01:12 MODE #accueil -o OverOp-H (retiré à la main)
16:03:00 BOTSTAT … OverOp-H #accueil
16:03:00 [INFO] mandat : +o OverOp-H sur #accueil — prêt actif
16:03:01 MODE #accueil +o OverOp-H (reposé par IriX)
Le droit accordé par un mandat se traduit maintenant en pouvoir réel, et se rattrape tout seul en cinq minutes au plus — netsplit, redémarrage, ou -o posé à la main.
⚠ Le +o du 10/08 n'était PAS celui du mandat, et il a fallu le TS pour le voir. #accueil était passé de 1786448615 à 1786449804 pendant un redémarrage : un TS qui change veut dire salon vidé puis recréé, et OverOp-H l'avait recréé en premier. Son op était celui de la création. La garde isop → continue faisait donc exactement son travail — il n'y avait rien à corriger, et deux heures ont failli être passées à chercher un défaut absent.
⚠ Le port du lien Anope était fabriqué, comme le nom l'avait été
_anope_configure proposait 7000 en dur — le port d'UnrealIRCd. Or la configuration d'Overtchat-Hybrid dit en toutes lettres pourquoi elle ne l'emploie pas : « 6900, et non le 7000 d'UnrealIRCd ». Le fichier a donc été écrit avec un port où rien n'écoute.
⚠ Une valeur fabriquée sur trois suffisait à tout casser. Le nom et le mot de passe étaient désormais lus dans la configuration du serveur ; le port, non. Et une connexion refusée ne dit pas qu'on s'est trompé de port — elle dit qu'elle a été refusée.
Créations
_anope_srv_port— le port du listenerserversonly, lu. ⚠ Le bloclistenporte sonportavant sesoptions: on accumule le bloc et on ne décide qu'à l'accolade fermante. Lire le port dès qu'on le voit rendrait celui du premierlistendu fichier — 6667, les clients.
Modifications
Bootstrap/tests/test-anope-lien.sh— 26 constats, 5 mutations. Le décor porte maintenant les trois listeners dans l'ordre réel, celui des clients en premier : c'est le seul agencement où l'erreur se voit.
Erreurs
⚠ La trace du mandat a été cherchée dans le mauvais journal. ::soc::log n'écrit pas dans IriX/runtime/IriX.log mais dans IriX/cmds/IriX-sql.log. Le premier ne montrait aucune ligne mandat : +o alors que le MODE était bien parti — de quoi conclure à un mécanisme muet, ou pire, à un appel vers une procédure inexistante. Elle existe, et elle avait écrit. Le registre nomme déjà ce piège : « les journaux ne sont pas là où on les cherche ».
4.16.32 — 11 août 2026
Le site annonçait 80 places libres. Une était occupée depuis deux heures.
La même logique — « combien de salons ce robot sert-il ? » — vivait à trois endroits, et les trois ne lisaient que ${t}_botreg, l'enregistrement OPMYBOT. Le prêt en cours sur #accueil n'était compté nulle part.
| Où | Corrigé en |
|---|---|
::soc::botreg::salons_attendus — l'écart d'état | 4.16.29 |
::soc::botreg::charges — le choix du robot le moins chargé | 4.16.30 |
botreg_robots() — le compte affiché au public | ici |
⚠ Le troisième s'est vu en rendant la page, pas en lisant le code. Les deux premiers corrigés, ?p=robots annonçait toujours « 80 places libres » : le compte du site est en PHP et fait sa propre requête. Corriger deux copies sur trois ne corrige rien — c'était la plus visible qui restait.
Le parc annonce désormais 79 places, constaté sur la base de production.
Modifications
Web/inc/botreg.php— la charge réunit les deux tables. ⚠ LeNOT INévite qu'un salon cité par les deux compte double. ⚠ Sans aucune des deux tables, la charge resteNULL— inconnue, pas nulle :botreg_placesécarte alors le robot au lieu de lui prêter toutes ses places.Web/tests/test-botreg.php— 129 constats, 4 mutations.
Créations
botreg_charge_sql()— l'expression SQL extraite, pure donc éprouvable. Elle était noyée dansbotreg_robots(), qui interroge la base et ne peut être appelée sans elle : la logique du compte n'était couverte par aucun constat, dans un banc qui en porte cent vingt.
Avertissements
⚠ Trois copies restent trois copies. Elles sont désormais d'accord, et rien ne les empêche de diverger à nouveau : deux sont en Tcl, une en PHP, et aucun banc ne les croise. Le banc du site vérifie déjà la péremption d'observation contre le module Tcl — le même geste vaudrait ici.
4.16.31 — 11 août 2026
La page Anope donne enfin la commande à taper — et un banc vérifie qu'elle existe encore.
?p=anope disait déjà l'essentiel : installation présente, configuration absente, unité arrêtée. Mais elle renvoyait à _anope_configure, une fonction interne du script, et décrivait un geste — « il demande alors l'hôte, le port et le mot de passe » — qui n'est plus le bon. Elle donne maintenant la commande, et dit que le nom et le mot de passe sont proposés.
⚠ Une page qui donne une commande doit être éprouvée comme du code
Une commande renommée, et cette page envoie l'exploitant sur « Option inconnue » — sans que rien ne le signale, puisque le texte reste parfaitement lisible. Le banc lit la commande dans la page et la cherche dans le script : à trois endroits, le dispatcher d'arguments, _cli_commande, et l'écran d'aide.
⚠ Il ne compare pas deux chaînes écrites à la main. Une constante recopiée dans le banc aurait divergé exactement comme le texte qu'elle prétend garder.
Modifications
Web/pages/anope.php— la commande, et ce qu'elle propose désormais seule.Web/tests/test-anope.php— 84 constats (78 avant), 2 mutations.
Erreurs
⚠ Le premier jet de ce banc laissait passer la mutation qu'il visait. Le constat sur l'écran d'aide cherchait la commande n'importe où après le mot USAGE, en mode /s : le motif traversait les dix mille lignes du script et retombait sur les occurrences du dispatcher. Retirer la commande de l'aide laissait donc le constat au vert. La sous-chaîne n'est pas une preuve — le banc extrait maintenant le bloc, puis cherche dedans.
⚠ Et une mesure trop large a fait accuser le panel à tort. Le rendu hors navigateur, qui sert à éprouver les pages, extrait le texte par strip_tags — lequel ignore l'attribut hidden. Une pastille masquée « v4.16.27 disponible » est ainsi ressortie comme si elle était à l'écran, et a été prise pour un défaut : le panel aurait annoncé une mise à jour vers une version plus ancienne que l'installée. Il n'en affichait rien, et update_verdict faisait exactement son travail. L'outil de mesure retire désormais ce qui est masqué avant de lire.
4.16.30 — 11 août 2026
Anope ne pouvait plus être configuré, et le nom qu'on allait lui donner n'existait nulle part. Trois défauts trouvés avant son premier démarrage.
Anope est compilé depuis le 30/07/2026. Son répertoire conf/ ne portait que les modèles livrés par Anope, et le service refusait donc de démarrer. Trois choses distinctes l'en empêchaient, chacune suffisante.
1. La configuration n'était plus joignable
_service_install rend 0 sur « installation existante valide ». Cette validité est celle du binaire — _service_verify ne regarde rien d'autre, et c'est très bien : un binaire absent et une configuration absente ne se réparent pas de la même façon. Mais la conséquence était qu'Anope compilé ne pouvait plus jamais atteindre _anope_configure. Relancer son installation s'arrêtait sur un message rassurant.
C'est le défaut que le commentaire d'_install_anope dit vouloir éviter — « l'installation réussie qui ne démarre jamais » — revenu par l'autre porte.
2. Le nom proposé n'était le nom de rien
Le nom du serveur de services se fabriquait sur l'identité du réseau : services.${reseau,,}, soit services.service-overtchat ici. Le bloc link du serveur IRC, lui, s'appelle services.overtchat.fr.
Anope se serait annoncé sous un nom que le serveur ne connaît pas. Le lien aurait été refusé, et un refus de lien ne dit jamais pourquoi il l'est. Le nom se lit désormais dans les blocs link du serveur ; un nom sans bloc correspondant est signalé, y compris quand il est saisi à la main.
3. Le mot de passe se ressaisissait alors qu'il est lisible
Ce mot de passe n'est pas un choix : il doit être identique à celui du bloc link. Le redemander ouvrait la seule porte par où une divergence pouvait entrer, pour un réglage qui n'avait aucune raison de varier. Il est maintenant proposé par défaut, et reste remplaçable à l'invite.
⚠ Il n'est jamais affiché — ni au calcul, ni à l'invite, qui ne nomme que le fichier d'où il vient. Un défaut qu'on montre est un secret qu'on publie.
Tranché par SerVuS le 11/08/2026.
Créations
anope-config— une commande, pour un geste qui n'en demandait pas moins de cinq. Elle écrit la configuration si elle manque et ne touche à rien si elle est là. ⚠ Elle pose des questions : l'écran d'aide la range à part des commandes utilisables depuis une tâche planifiée._anope_srv_conf,_anope_link_noms,_anope_link_pass— la lecture du bloclink. ⚠ Le registre des serveurs ne suffisait pas : son champconfigvautnullet la configuration en service vit hors du dépôt._service_configure_si_besoin— le rattrapage. Anope seul, par constat et non par principe : c'est le seul des trois services dont on ait vérifié qu'il refuse de démarrer sans configuration.Bootstrap/tests/test-anope-lien.sh— 22 constats, 3 mutations.
Modifications
IriX/cmds/irix-botreg.tcl—chargescompte désormais parsalons_attendus. Un robot ne tenant que des prêts s'annonçait à0/20et pouvait se voir confier vingt salons de plus. Tranché par SerVuS : un prêt occupe une place du parc. ⚠ Le dédoublonnage n'est pas un détail : deuxCOUNT(*)additionnés compteraient deux fois un salon que les deux tables citent, et rempliraient le parc plus vite qu'il ne se remplit.Tools/bancs.sh— les bancsbashsont découverts.
⚠ Le lanceur annonçait découvrir, et ignorait une famille entière
Tools/bancs.sh n'avait aucun motif bash. Bootstrap/overtchat-setup.sh fait dix mille lignes, décide de l'installation entière, et n'était couvert par rien. Le fichier porte pourtant en tête : « il DÉCOUVRE, il n'énumère pas ».
Une famille sans motif ne se voit pas. Le lanceur ne se plaint jamais d'un banc qu'il ignore — il annonce « tout est au vert », ce qui est vrai de ce qu'il regarde.
Erreurs
⚠ Une mutation a survécu au premier jet du banc, et elle avait raison. Le constat qui gardait l'arrêt à l'accolade fermante ne prouvait rien : tant que le bloc visé porte un mot de passe, la lecture sort dessus et la garde ne sert jamais. Il fallait un bloc link sans mot de passe, suivi d'un bloc oper qui en porte un — alors l'absence de garde se voit, et le lien hérite du secret d'un opérateur.
Le cas manquait au décor, pas au code. Sans la passe de mutation, ce banc serait resté vert en ne mesurant rien — c'est le fil du 10/08, appliqué cette fois à un banc écrit le jour même.
4.16.29 — 11 août 2026
Le salon prêté n'est plus signalé « en trop » — un robot à sa place ne produit plus d'erreur toutes les cinq minutes.
Le premier prêt du projet a tenu vingt-quatre heures. Pendant tout ce temps, chaque état poussé par OverOp-H — un toutes les 5 minutes — écrivait une ligne d'erreur en base, disant que le robot siégeait sur un salon qu'on ne lui avait pas attribué. Il y était pourtant par un prêt en règle.
La cause : deux raisons de siéger, une seule table lue
Un robot sert un salon pour l'une de deux raisons, et elles vivent dans deux tables qui ne se recoupent pas :
| Table | Ce qu'elle dit |
|---|---|
${t}_botreg | l'enregistrement OPMYBOT : un propriétaire a déclaré son robot, pooshy dit lequel des nôtres le sert |
${t}_mandat | le prêt : botnick est le robot prêté, et seul un mandat actif compte |
::soc::botreg::salons_attendus ne lisait que la première. Le robot, lui, annonce tous ses salons. Tout salon tenu au titre d'un prêt tombait donc dans le « en trop ».
Ce que la mesure a donné
Sept fausses erreurs en trente-cinq minutes, sur une table qui en comptait treize en tout. Le prêt courait sur vingt-quatre heures : deux cent quatre-vingt-huit lignes. Le commentaire placé juste au-dessus de cette journalisation explique qu'on ne trace que l'écart pour ne pas noyer cette table — c'est très exactement ce qui se produisait.
⚠ Aucun dégât sur le réseau. Rien n'est corrigé automatiquement : la décision du 09/08 — regarder vivre avant de laisser une machine en corriger une autre — tenait le piège fermé. Un correcteur d'écart branché sur cette mesure aurait fait sortir un robot d'un salon où son prêt était valide.
Modifications
IriX/cmds/irix-botreg.tcl—salons_attendusréunit les deux sources. ⚠ Deux requêtes, pas unUNION: les deux tables peuvent porter des collations différentes selon qu'elles sont nées d'une migration ou du Tcl, et unUNIONlèverait 1271 sur la divergence. Chaque source sous son proprecatch, pour qu'un déploiement partiel rende encore l'autre.IriX/tests/test-botreg-proto.tcl— huit constats neufs, dont celui de la jointure. Vérifiés contre l'ancien code : six échouent, le banc attrape donc bien le défaut.
Avertissements
⚠ ::soc::botreg::charges garde le même angle mort, et ce n'est pas corrigé ici. Elle compte la charge d'un robot sur ${t}_botreg seul : un robot qui ne tiendrait que des prêts s'annonce à 0/20 et pourrait se voir confier vingt salons de plus. Sans effet aujourd'hui — un seul prêt, aucun enregistrement OPMYBOT —, mais c'est une décision de conception à prendre : un prêt occupe-t-il une place du parc ? En attente d'arbitrage.
Erreurs
⚠ Le piège des listes Tcl, refait pour la troisième fois — et pour la deuxième fois dans le banc qui l'annonce. Trois constats neufs comparaient la liste rendue par salons_attendus à #alpha #beta ; une liste dont le premier élément commence par un dièse se représente {#alpha} #beta. Les trois ont échoué sur du code juste.
Le connaître ne suffit pas : l'avertissement était écrit dans ce fichier, à quelques lignes. D'où verifie_salons, qui compare longueur puis chaque salon par lsearch -exact — une procédure qui rend le geste faux impossible, plutôt qu'un commentaire de plus.
4.16.28 — 11 août 2026
Le robot prêté reçoit enfin son op — et la garde qui empêche de le donner à un usurpateur.
Un mandat accorde kick, ban, voice. Le robot ne peut rien en faire tant qu'il n'est pas opérateur du salon. Le premier prêt réel l'a montré : OverOp-H est entré sur #accueil et y a siégé sans le moindre pouvoir. Le droit était accordé, pas le moyen de l'exercer.
⚠⚠ Pourquoi pas autoop, qui semblait fait pour ça
Demandé par SerVuS, mesuré avant d'écrire : le réglage n'aurait donné l'op à personne. do_autoop le pose d'après le grade par salon (chanaccess::effectif) ; or chan_access est vide et les robots n'ont aucun compte. Un robot en sort par le continue, sans un mot. L'armer aurait été une case sans effet — le travers que cet écran corrige partout ailleurs.
⚠⚠ Et surtout : pourquoi pas sur le pseudonyme
Mesuré le même jour, et c'est une trouvaille à part entière : les pseudonymes OverOp-* ne sont protégés par rien. Le serveur charge conf/pseudos-proteges.conf et y trouve zéro entrée — le journal le dit à chaque démarrage —, et nickguard ne les couvre pas davantage, faute de compte en base.
Oper quiconque porte le nom d'un robot déclaré aurait donc offert l'opérat à qui prend ce pseudonyme pendant une absence. C'est très exactement le défaut de Pooshy que ce chantier existe pour ne pas refaire : un pseudonyme s'usurpe.
La garde retenue : l'UID, pas le nom
L'op se pose depuis traiter_stat, au BOTSTAT que chaque robot pousse toutes les 5 minutes. À cet endroit, la source a été authentifiée par son UID et le nom annoncé recoupé avec le registre : le robot qui parle est celui qu'il dit être.
⚠ Périodique plutôt que ponctuel : l'état revient toutes les 5 minutes, ce qui rattrape seul un netsplit, un redémarrage du serveur ou un -o posé à la main. Poser l'op une seule fois, à l'entrée, aurait tenu jusqu'au premier incident.
⚠ Ce n'est PAS « corriger l'écart ». La décision du 09/08 — regarder vivre avant de laisser une machine en corriger une autre — n'est pas touchée : on ne fait entrer ni sortir personne, on donne son pouvoir à un robot déjà présent sur un salon dont le prêt est en cours.
Créations
::soc::mandat::oper_si_prete— six refus, chacun fermant une porte distincte : pas de mandat, mandat fini, un autre robot que le mandataire, IriX absent, IriX présent mais pas op, robot parti entre son annonce et l'op.
Modifications
IriX/tests/test-mandat.tcl— 116 constats, 7 mutations toutes détectées, dont celle qui compte : n'importe quel robot recevrait l'op.
Erreurs
⚠ Une mutation survivante a révélé un bouchon trop grossier. Le décor du banc faisait rendre la même liste à botonchan et botisop : la mutation qui retire le contrôle d'op survivait donc, le contrôle de présence filtrant déjà. Un bouchon plus permissif que la réalité ne prouve rien — et IriX peut parfaitement siéger sans être opérateur, c'est même l'état normal après un netsplit. Les deux états sont désormais distincts, et le cas a son constat.
⚠ Et le piège des listes Tcl, refait dans le banc qui l'annonce. Un constat comparait la liste rendue à #accueil ; une liste dont le premier élément commence par un dièse se représente {#accueil}, Tcl protégeant le caractère qui ouvrirait un commentaire. Comparer [lindex $p 0], jamais la liste rendue telle quelle.
4.16.27 — 11 août 2026
Le premier prêt de robot du projet qui aboutit — et le défaut qui l'empêchait depuis toujours, au bout du chemin le plus visible.
Signalé par SerVuS : « le système opmybot est paralysé ». L'audit a montré que tout circulait — les quatre robots poussent leur état toutes les 5 minutes, IriX les reçoit sur le lien S2S, les verbes concordent, consommer tourne toutes les 30 secondes, les commandes existent. Et pourtant rien n'arrivait.
Accorder le droit ne fait venir personne
::soc::mandat::consommer écrivait le mandat et s'arrêtait là : aucun ordre d'entrée ne partait, et l'accord n'était pas journalisé.
::soc::mandat::installer existait pourtant depuis le 05/08 — écrite exactement pour cela — mais elle n'avait qu'un seul appelant : la commande IRCop MANDAT. Le chemin du site, celui qu'un propriétaire emprunte vraiment, ne l'appelait jamais.
Mesuré en déposant une vraie demande : demande passée à done, mandat actif, « OverOp-H prêté à #accueil » au journal du bot — et le robot annonçant toujours « aucun salon » cinq minutes plus tard. Le panel annonçait un robot accordé, le salon ne voyait rien venir.
⚠ C'est la confirmation mensongère au bout du chemin le plus visible : celui d'un utilisateur ordinaire, qui n'a aucun moyen de savoir que le droit lui a été accordé sans que le robot en soit prévenu.
⚠ Aucun banc ne pouvait le voir. Ils éprouvaient accorder et installer séparément, et toutes deux étaient justes. Le trou était à la jointure — là où un module suppose qu'un autre fait le travail. C'est le quatrième du même genre sur ce chantier, et il n'apparaît qu'en faisant tourner la chaîne entière.
✅ Constaté de bout en bout, à la seconde
13:44:50 demande #4 déposée en « approved », comme le panel l'écrit
13:44:53 SOC MANDAT: OverOp-H prêté à #accueil pour SerVuS
13:44:53 mandat_log : acte=accord, result=ok ← l'accord est tracé
13:44:53 RAW OUT: BOTCMD 1 o…-1 JOIN #accueil ← l'ordre part
13:44:53 robot : « #accueil activé, entrée en cours »
13:44:54 OverOp-H joined #accueil. ← il entre
13:45:03 BOTACK 1 o…-1 done #accueil ← il accuse réception
13:45:23 opmybot : #accueil | OverOp-H | chanq_robots ← la présence est constatée
Neuf maillons, trente-trois secondes. Le chanfile du robot est passé de +inactive à -inactive sur #accueil : entrer réactive un salon connu plutôt que d'émettre un JOIN sec — c'est ce qui le fait revenir seul après un netsplit.
⚠ Et ia_tokens est resté à 0 : l'essai n'a rien fait apprendre au modèle.
Modifications
IriX/cmds/irix-mandat.tcl—consommerjournalise puis envoie, dans cet ordre : un accord dont on perdrait la trace serait pire qu'un robot en retard.
⚠ Quand l'ordre ne part pas, le mandat TIENT — c'est un droit, et il est écrit. Mais la demande porte alors l'échec de l'arrivée, en nommant ce qui reste vrai : « le droit tient, faire entrer le robot à la main ». Annoncer « done » laisserait le demandeur devant un salon vide.
IriX/tests/test-mandat.tcl— 102 constats, 6 mutations toutes détectées, dont le défaut d'origine et son inverse : envoyer l'ordre alors que l'accord vient d'être refusé.
Ce que cela débloque
Le point 2 du registre attendait ceci depuis le début : BOTSTAT a enfin quelque chose à comparer. Jusqu'ici les robots servaient zéro salon et la base ne leur en attribuait aucun — deux listes vides ne divergent pas, et la bascule de charge ne pouvait pas s'écrire faute d'écarts à lire.
Avertissements
⚠ Le prêt d'essai est ACTIF pour 24 h — OverOp-H siège sur #accueil jusqu'au 12/08 13:44. Il s'éteindra seul ; MANDATFIN #accueil le termine plus tôt.
⚠ Le robot n'est pas opérateur du salon. Le mandat lui accorde kick,ban,voice, mais l'op se pose par autoop, qui n'est pas armé sur #accueil. Le droit et le pouvoir sont deux choses, et ce lot n'a prouvé que le premier.
4.16.26 — 11 août 2026
Sur les cinq symboles qui s'affichaient en clair sur la page publique, trois étaient corrects — c'est la mesure qui accusait à tort.
Un relevé du matin annonçait cinq symboles de mise en forme laissés bruts sur ?p=versions. En cherchant chacun dans la source, trois se sont révélés légitimes : ce sont des accents graves à l'intérieur d'un <code>, c'est-à-dire son contenu voulu. VERSION.md cite délibérément `~~texte~~` pour décrire le barré, et `IriX_channels` pour montrer qu'une table s'écrit avec ses accents graves.
⚠ Le relevé faisait strip_tags puis comptait. Il ne distinguait pas un symbole dans du code — voulu — d'un symbole laissé brut dans le texte — le défaut. Le banc, lui, avait déjà la bonne règle et écartait les <code> : c'est le contrôle improvisé qui était trop large.
Le défaut réel : un italique dans du gras
**Page *Liens ULine* honnête** affichait ses quatre astérisques. Le motif du gras interdisait tout astérisque à l'intérieur ([^*]+), si bien qu'un italique imbriqué le faisait échouer tout entier : l'italique sortait, le gras restait en clair. Depuis le 29 juillet.
⚠ Le contenu accepte désormais un astérisque isolé, jamais deux. Sans \*(?!\*), un motif gourmand fondrait **a** et **b** en un seul gras.
⚠ Et cette entrée l'a illustré toute seule : citer deux astérisques en texte nu les laisse forcément en clair. Pour les montrer, il faut les écrire comme du code — ce que le fichier fait déjà pour les tildes. Le moteur n'a pas à deviner qu'on parle DE la syntaxe au lieu de l'employer. Le banc neuf l'a signalé dans la minute qui a suivi l'écriture.
⚠⚠ Le banc surveillait quatre symboles sur cinq
test-markdown.php éprouve le vrai contenu des deux fichiers servis depuis le 10/08, et il écarte correctement les <code>. Il vérifiait qu'aucun tilde, aucun accent grave, aucun italique et aucun délimiteur de bloc ne restait en clair — et pas le gras. C'était le seul non surveillé, et c'est celui qui est passé.
La leçon n'est pas « le moteur avait un trou » mais « la SÉRIE en avait un » : une famille de contrôles qui couvre quatre cas sur cinq rassure autant qu'une qui les couvre tous.
Modifications
Web/inc/markdown.php— le motif du gras.Web/tests/test-markdown.php— 104 constats, dont le gras sur le vrai contenu, l'italique imbriqué, et les deux gardes d'espace.
Erreurs
⚠ Un constat neuf n'éprouvait pas ce qu'il croyait. Écrit pour tenir la garde d'espace ouvrante, il portait sur « ** texte ** » — blancs des deux côtés, un cas que la garde fermante refuse déjà seule. La mutation correspondante survivait, et le constat passait au vert sans rien tenir. Pour éprouver une garde, il faut le cas où elle est SEULE à jouer.
⚠ Et une mutation survivante ne prouve pas toujours un trou. Celle qui rendait le contenu du gras gourmand (+? → +) survit aussi — mesurée sur six cas, elle rend exactement le même HTML : \*(?!\*) interdit déjà de traverser un **. Elle est équivalente, aucun constat ne peut la détecter, et en fabriquer un reviendrait à éprouver la lettre du code plutôt que ce qu'il fait. Les compter ensemble aurait annoncé deux trous là où il n'y en avait qu'un.
4.16.25 — 11 août 2026
Le réglage autobans ne commandait rien, et personne ne s'en était aperçu parce qu'il n'avait jamais été ouvert.
Il attendait depuis des semaines qu'un ban existe : IriX_bans était vide, et l'ouvrir sur du vide aurait offert une case sans effet. SerVuS a posé le premier !newban réel ce jour-là. Mais en mesurant avant d'ouvrir, un défaut plus grave est tombé.
Deux chemins, un seul obéissait
Chemin, tous deux sur bind join - "*" | Consultait le réglage ? |
|---|---|
::soc::auto::on_join → check_autobans | oui |
::soc::evt_join, bloc en ligne | non |
Les bans persistants s'appliquaient donc partout, quoi qu'on coche — et deux fois quand la case l'était. Le bloc en double est retiré.
⚠ Conséquence assumée, tranchée par SerVuS : les bans persistants ne s'appliquent plus que sur les salons où autobans vaut oui. Il vaut non partout au 11/08 — la case est à cocher là où on veut qu'ils agissent.
⚠⚠ Et sans cette garde, la case aurait été une arme
check_autobans n'exemptait personne. Un masque assez large — et *!*@* est écrivable par un !newban d'op — faisait kicker au JOIN quiconque entre, le robot et les opérateurs du réseau compris. Un robot qui se kicke du salon qu'il tient n'y revient pas.
⚠ L'exemption posée le matin même dans le serveur n'y changeait rien : elle dispense du +b, pas du putkick, qui ne demande l'avis de personne. Deux gardes à deux étages, et il fallait les deux. Une protection posée dans une couche ne couvre pas ce qu'une autre couche fait de son propre chef.
⚠ Un op de salon n'est pas exempté : sanctionner un habitué turbulent est précisément l'usage de ce réglage. Deux des huit mutations gardent ce défaut inverse.
Modifications
IriX/cmds/IriX-sql.tcl— le chemin en double retiré, les deux gardes posées avant la lecture de la table : après, on aurait déjà posé le+bau moment de découvrir qu'il fallait épargner la personne.Web/inc/chanreglages.php—autobansdans les trois listes qui doivent s'accorder.Web/site/salonadmin.php— la case, et ce qu'elle ne fait pas.
⚠ La page dit deux choses qu'un propriétaire ne peut pas deviner : les bans du réseau (channel = '*') s'appliquent aussi, et décocher n'enlève pas les +b déjà posés. Là non plus ce n'est pas un oubli — un +l est un plafond calculé que le robot doit défaire, un +b est une sanction que quelqu'un a voulue.
Web/tests/test-chanreglages.php— 205 constats, 8 mutations toutes détectées.
Erreurs
⚠ Huit mutations ont d'abord été rapportées « PLANTÉE » — c'est-à-dire aucune mesure. Le bac à sable ne portait que trois fichiers de Web/inc/, et chanreglages.php en charge six par cascade. Le rapport avait l'allure d'un résultat. Un banc qui ne démarre pas ne dit rien, et il faut le distinguer d'un banc qui passe — c'est la quatrième issue des scripts de mutation, et elle vient de servir une fois de plus.
Avertissements
⚠ Reste à constater : cocher la case sur un salon et vérifier les deux moitiés — qu'un masque visé est bien expulsé, et surtout qu'un opérateur du réseau ne l'est pas.
4.16.24 — 11 août 2026
Un opérateur du réseau enfermé dehors par son propre ban. SerVuS s'est banni lui-même sur #!Overtchat! pour éprouver la chaîne des bans, et s'est retrouvé incapable de parler sur le salon — donc incapable d'y lancer la commande qui l'aurait débanni.
⚠ Son client lui a dit « mode modéré (+m) », et c'était faux. Le salon ne portait que +tn et +l : c'est le ban qui bloquait. Le serveur avait bien répondu Cannot send to channel (you're banned), mais un client remplace le texte du numérique 404 par son propre libellé. Même piège que le 491 de OPER — devant un refus inexpliqué, lire le journal du serveur, jamais la traduction du client.
La ligne de partage, et ce n'est pas « l'opérateur peut tout »
Une seule question décide : qui cette restriction protège-t-elle ?
| Elle protège le salon de QUELQU'UN | Elle promet quelque chose AUX MEMBRES |
|---|---|
+b, +m, mute, +i, +k, +l, l'attente après expulsion | +z (tous en TLS), +R et +M (tous identifiés), +O |
| L'opérateur passe : ce sont des mesures prises contre quelqu'un, et il est celui qui doit pouvoir intervenir malgré elles | L'opérateur ne passe pas : les outrepasser rendrait la promesse mensongère pour des gens qui n'ont aucun moyen de le savoir |
⚠⚠ Et l'exemption se DIT. Une exemption silencieuse est un piège : quelqu'un qui pose un ban sur lui-même pour l'éprouver, et que le ban n'arrête pas, conclut que les bans ne fonctionnent pas. C'est exactement ce qui était en train de se passer.
#essai : un ban vous vise, votre statut d'opérateur du réseau vous en dispense.
Créations
Servers/overtchat-hybrid/tests/test-oper-outrepasse.py— 22 vérifications, 5 mutations toutes détectées.
⚠ Elle garde les deux familles à la fois. Vérifier seulement que l'opérateur passe outre un ban laisserait vivre la version « l'opérateur peut tout », qui est un autre défaut — et celui-là se paierait sur des membres qui croient à une promesse. Deux des cinq mutations gardent précisément ce défaut-là.
Modifications
Servers/overtchat-hybrid/src/channel.c—channel_can_joinetchannel_can_send.
⚠ Le contrôle établit d'abord ce qui s'appliquerait, avant de décider. Répartir un « si opérateur, sortir » dans chaque branche aurait deux défauts : la notice ne saurait plus dire laquelle des restrictions joue, et la prochaine restriction ajoutée oublierait l'exemption sans que rien ne le signale.
Servers/overtchat-hybrid/tests/run-tests.sh— seize campagnes. ⚠ La liste est tenue à la main et le fichier le dit lui-même ; les numéros ont dû être repris un par un,Tools/bancs.shdérivant d'eux le nombre annoncé.
Avertissements
✅ EN SERVICE depuis 12h50, avec l'accord de SerVuS. Le réseau est revenu entier : huit unités actives, les cinq pseudo-clients réintroduits, les quatre robots présents, dix connectés en base, et le serveur répond son 001.
⚠ L'ancien binaire est conservé en bin/overtchatd.avant-4.16.24 — le retour arrière tient en un mv et un restart.
⚠ Deux pièges pendant la pose, tous deux déjà nommés au carnet. On n'écrase pas un binaire en cours d'exécution : on copie à côté puis on mv, qui est atomique, jamais install qui tronque. Et sudo exige le suffixe .service — sans lui, la commande a demandé un mot de passe et est restée suspendue, le binaire déjà remplacé et le service pas encore redémarré. Les connaître ne suffit pas à ne pas les faire.
⚠ Reste à constater sur le vrai réseau : se rebannir, vérifier qu'on parle toujours et que la notice arrive.
⚠ Une mesure a failli être prise pour bonne alors qu'elle n'avait pas eu lieu : un cp écrit en ligne dans PowerShell a perdu ses variables sans erreur, si bien que la seizième campagne n'a pas tourné — et le lanceur a quand même affiché « toutes les campagnes passent ». Une mesure qui n'a pas eu lieu n'est pas une mesure réussie, et c'est la troisième fois que ce projet le paie.
4.16.23 — 11 août 2026
La première pose de robot qui va d'un bout à l'autre — et le quatrième défaut du chantier, qui attendait trois semaines qu'un hello soit donné.
SerVuS a envoyé /msg OverOp-T hello à 11h35. Le minuteur est repassé cinq minutes plus tard et a fait tout le reste seul, en six secondes :
11:40:13 userfile trouvé — le hello a eu lieu.
11:40:19 ✅ OverOp-T est sous systemd et actif.
Constaté juste après : overop@t.service active et enabled, l'unité transitoire inactive, le processus relancé sous la forme -n de systemd, et la demande du pool passée à done.
⚠⚠ Et le robot était invisible du parc
Posé, actif, connecté au réseau — et ${t}_botreg_pooshy ne le connaissait pas. L'exécutant s'arrêtait à systemctl is-active sans jamais déclarer le robot.
Conséquence : ::soc::botreg::charges ne le voyait pas, aucun salon ne pouvait lui être confié, le site n'annonçait aucune place de plus — et la demande s'écrivait quand même « done ». Une pose qui aboutit à un robot inutilisable est une pose qui a échoué.
⚠ Ce défaut ne pouvait pas se montrer plus tôt : il fallait qu'un hello soit donné pour de vrai. C'est le quatrième étage de la pile, et la démonstration une fois de plus qu'un mécanisme qu'on n'a jamais fait tourner en entier n'a pas un défaut, il en a plusieurs.
Modifications
Tools/overop-pool.php— le robot est déclaré au parc avecorigin='auto', une valeur qui existait dans le schéma depuis l'origine sans que personne ne l'écrive : elle distingue au panel ce que le pool a posé de ce qu'un exploitant a déclaré.
⚠ Sans rien écraser si la ligne existe déjà. Un robot déclaré à la main porte peut-être un max_chans choisi ou un enabled='non' volontaire ; les remplacer par les valeurs par défaut défairait une décision d'exploitation. C'est le travers du seed « INSERT IGNORE » de juillet.
⚠ Et une déclaration ratée n'écrit pas « failed » : le robot est posé et actif, dire le contraire pousserait à le reposer. C'est le détail de « done » qui porte la réserve.
Web/tests/test-botpool.php— neuf constats de plus (49 en tout).
✅ CONSTATÉ : OverOp-T a été rattrapé à la main, sa demande étant déjà close, et il est passé online tout seul au BOTSTAT suivant. Le parc annonce quatre robots et 80 places.
⚠ !b n'est pas !newban, et la différence est tout l'enjeu
Essayé le même jour sur #!Overtchat! : !b SerVuS a bien posé le MODE +b *!*@… à 11h43:51, retiré à 11h44:37. La commande marche.
Mais ::soc::pub_ban pose un mode et journalise, sans rien écrire en base : c'est un ban de salon, volatil, que le bot ne repose pas. ${t}_bans est restée vide, et le réglage autobans reste donc bloqué exactement comme avant l'essai. Seule !newban <masque> [durée] [raison] écrit la ligne persistante que check_autobans consulte au JOIN.
Deux commandes voisines par le nom, deux mécanismes sans rapport.
✅ Et l'essai n'a rien appris à l'IA : ia_tokens est resté à 0, contrairement aux !newban du 10/08 qui avaient fait apprendre seize jetons. ⚠ Le vérifier après coup reste la règle — c'est en mesurant qu'on le sait.
4.16.22 — 11 août 2026
La protection des pseudonymes promettait à un visiteur ce qu'elle ne pouvait pas faire. Sur un réseau parlant P10, elle lui écrivait « le pseudo vous sera retiré dans 60 secondes », puis « vous êtes désormais Invite12345 » — et ne renommait personne.
P10 n'a aucun équivalent de SVSNICK. ::soc::proto::pour levait donc, comme elle doit le faire ; mais l'appelant travaille sous le catch de son propre appelant, et l'erreur y mourait sans un mot. Deux promesses au visiteur, aucun effet, et le titulaire légitime du pseudonyme croyait celui-ci protégé.
⚠ Une garde qui annonce ce qu'elle ne fait pas est pire qu'une garde absente : elle rassure à tort.
operjoin échouait de la même façon, en silence complet cette fois : un opérateur constatait qu'il n'entrait pas dans son salon, et rien nulle part ne disait pourquoi.
Ce qui n'a PAS changé
P10 ne saura jamais renommer, et ce n'est pas un défaut. Six primitives n'ont aucun équivalent dans ce dialecte — c'est une propriété du protocole. La règle reste la même : ::soc::proto::pour lève plutôt que d'inventer un repli. On a seulement donné aux appelants le moyen de ne pas l'atteindre.
Créations
- Deux aides dans
irix-proto.tcl:sait_sur, qui demande sans lever, etsignaler_absence, qui journalise une seule fois par dialecte et par fonction.
⚠ Ce « une seule fois » n'est pas du confort. La garde passe une fois par visiteur non identifié : écrire à chaque passage remplirait le journal du même constat et noierait tout le reste.
IriX/tests/test-proto-absence.tcl— 38 constats, 9 mutations toutes détectées, dont celle qui compte : déplacer le contrôle APRÈS la notice rétablit le défaut mot pour mot, et le banc le voit.
Modifications
irix-nickguard.tcl— sur un lien qui ne sait pas renommer, la garde retombe sur son comportement désarmé. L'avertissement part quand même : il informe le visiteur que le pseudonyme est enregistré, ce qui le concerne, et ne lui promet plus rien. Aucune minuterie n'est posée.
⚠ L'ordre est tout le correctif. Le contrôle est avant la notice, dans les deux procédures.
irix-operjoin.tcl— même chose pour l'entrée dans les salons d'opérateurs et pour le mode qui les réserve. ⚠ Le contrôle y précède même la requête SQL : interroger la base pour lever ensuite sur chaque salon coûterait une requête pour rien.
Avertissements
⚠ Ce lot corrige le silence, pas l'échec. Sur un réseau P10, la protection des pseudonymes reste un avertissement sans suite, et les salons d'opérateurs ne sont pas réservés. C'est ce que la table des capacités disait déjà au panel ; c'est désormais aussi ce que dit le journal, et ce que le visiteur ne s'entend plus promettre.
4.16.21 — 11 août 2026
Une page qui pilotait un service envoyait sans jamais savoir ce qui revenait. La colonne raw_reply existait depuis l'origine du schéma et n'était ni écrite ni lue : un SVSNICK refusé, un pseudonyme inconnu, un mode invalide — le panel affichait « sent » avec la même sérénité qu'une commande ait abouti ou qu'elle ait été rejetée.
⚠⚠ Ce n'est pas une corrélation, c'est une proximité
Et cela devait être dit plutôt qu'habillé. Le protocole IRC n'a aucun identifiant de requête : rien, dans une ligne entrante, ne dit à quelle commande elle répond. IriX retient donc ce qui arrive sur le même lien dans les 5 secondes suivant l'envoi, et la colonne porte en tête une mention qui le rappelle.
⚠ Cette mention n'est pas décorative. Sans elle, quelqu'un finirait par lire ces lignes comme la réponse certaine à sa commande — et un outil qui laisse croire à une certitude qu'il n'a pas vaut moins que pas d'outil.
Trois garde-fous, chacun contre une façon précise de se tromper :
| Garde-fou | Contre quoi |
|---|---|
| Une seule commande en attente par lien | ⚠ Deux commandes lancées coup sur coup se voleraient leurs réponses, et le panel afficherait une erreur sous la mauvaise ligne. Pire que pas de réponse du tout. Si une seconde part, la première est refermée avec ce qu'elle a |
| Seul ce qui ressemble à une réponse | Numériques, NOTICE, ERROR. Le trafic ordinaire d'un lien serveur — SJOIN, PRIVMSG, MODE, PING — noierait la vraie réponse |
| Une fenêtre courte, des plafonds | 5 secondes, 20 lignes, 4 000 octets. Au-delà, ce qui arrive n'a probablement plus de rapport |
⚠ L'accroche est dans on_read, pas dans le dispatcheur. Les ERROR et les PING sont traités avant lui et ne descendent jamais jusque-là — or un ERROR est justement la réponse la plus utile à rattacher. Et elle est sous catch : une capture de confort ne doit jamais empêcher une ligne d'être traitée.
⚠ Le silence ne s'écrit pas. La plupart des commandes serveur-à-serveur n'appellent aucune réponse. Remplir la colonne pour dire « rien » ferait passer ce silence normal pour un événement, et noierait le panel de bruit.
Créations
IriX/tests/test-ulcmd-retour.tcl— 44 constats, 11 mutations toutes détectées. Les procédures sont extraites du fichier servi, jamais recopiées.
Modifications
IriX/cmds/IriX-sql.tcl— cinq procédures de capture, et les deux accroches.
⚠ Un piège de Tcl évité de justesse. Le premier jet déclarait ses constantes par variable reponse_fenetre. Ce fichier n'ouvre aucun namespace eval — ses procédures sont déclarées par leur nom qualifié —, si bien que ce variable aurait posé ::reponse_fenetre, dans le namespace global. Les procédures auraient lu une variable inexistante et la fenêtre aurait valu la chaîne vide. C'est la forme set au nom complet qu'emploie déjà tout le sous-système ; un constat du banc l'exige désormais.
Web/pages/ulines.php— colonne « Retour » dans la file des commandes. La mention de proximité est écartée de l'aperçu, où elle serait identique partout, et conservée dans l'infobulle, où l'on lit vraiment le détail.
Avertissements
⚠ Le tampon vit en mémoire jusqu'à la fermeture. Un redémarrage du bot pendant la fenêtre perd la capture en cours : la commande reste « sent » sans retour — c'est-à-dire exactement l'état d'avant. Rien n'est dégradé.
⚠ Reste à constater sur un vrai lien. La capture n'a été éprouvée que hors réseau. Il y faut une commande qui provoque une réponse — par exemple !ulcmdq <lien> WHOIS Inexistant depuis IRC —, puis la colonne « Retour » de ?p=ulines.
4.16.20 — 11 août 2026
Il n'existait aucune sauvegarde de cette installation. Le mécanisme est écrit depuis juillet, documenté sur deux cent trente lignes, éprouvé — et il n'avait jamais tourné une seule fois.
Mesuré ce jour : ~/.config/overtchat/backup.pass absent, et ~/Sauvegardes/ ne portant qu'un dump SQL manuel du 25 juillet.
BACKUP.md conseillait un crontab -e, et notait deux lignes plus bas que WSL ne démarre pas cron. La consigne se contredisait elle-même : la suivre à la lettre ne sauvegardait rien, et rien ne le disait. Le repli proposé — le Planificateur de tâches Windows — sort du projet, ne laisse aucune trace dans journalctl et ne se réinstalle pas avec lui.
La première exécution réelle a rendu deux défauts
Ni l'un ni l'autre ne se lisait dans le code. C'est le motif déjà payé quatre fois sur la pose de robot : un mécanisme qu'on n'a jamais fait tourner en entier n'a pas un défaut, il en a une pile.
⚠⚠ L'archive pesait 201 Mo pour 180 Ko annoncés. Un facteur mille. Le seul motif d'exclusion visant les composants tiers pointait un niveau trop haut : Anope pose son arbre un cran plus bas. Le motif ne matchait rien du tout, et un motif qui ne matche rien ne produit aucune erreur — 881 Mo de sources, de modules compilés et de binaires partaient dans chaque archive.
⚠ Le correctif ne pouvait pas être « exclure Anope ». anope/conf porte la configuration réelle du service, mots de passe des liens compris, et n'est ni dans le dépôt ni retéléchargeable. Même raisonnement que pour Servers/, déjà écrit dans BACKUP.md : ce qui n'existe nulle part ailleurs ne s'exclut pas, si petit soit-il. Résultat : 2,9 Mo, et 3,0 Mo avec la base.
⚠⚠ Et --restore <archive> <cible> était impossible. do_restore accepte la cible depuis toujours, l'aide du script la documente, et BACKUP.md insiste pour qu'on l'emploie — « toujours dans un dossier NEUF, jamais par-dessus l'existant ». Mais l'analyse d'arguments ne la consommait pas : le dossier tombait dans « Option inconnue » et le script mourait.
La seule façon documentée de restaurer sans risque ne fonctionnait pas — et c'est le geste qu'on fait le jour où l'on a tout perdu, donc le plus mauvais moment pour découvrir un défaut.
Créations
Config/systemd/overtchat-backup.{service,timer}— un passage par jour à 4 h, avec la base.
| Choix de l'unité | Contre quoi |
|---|---|
Persistent=true | ⚠⚠ La machine est un poste de bureau : elle ne tourne pas à 4 h. Sans cette ligne le minuteur ne partirait jamais, et cela ne se verrait pas — il serait « active » pendant que le service n'a jamais tourné |
Aucun SuccessExitStatus | Les deux autres minuteurs du projet en portent un, à raison. Ici, une sauvegarde ratée comptée pour un succès ne se découvrirait qu'en restaurant |
PrivateTmp=true | Le dump SQL transite en clair par un répertoire temporaire. L'archive, elle, n'est jamais écrite en clair — gpg chiffre à la volée |
Web/tests/test-backup.php— 42 constats, 9 mutations toutes détectées. Il éprouve les motifs d'exclusion contre des chemins témoins, comme tar les appliquerait. ⚠ Un premier jet ne testait que les préfixes et accusait le script de ne pas exclure le dépôt git : sans--anchored, un motif tar matche aussi un suffixe de composants. C'était le contrôle qui avait tort.
Modifications
Tools/backup-overtchat.sh— les exclusions d'Anope, la cible de restauration, et trois durcissements :
⚠ --with-db échoue au lieu d'avertir. Mot de passe introuvable, mysqldump absent, dump refusé, dump vide : ces quatre cas étaient de simples avertissements, l'archive se créait, le script rendait 0, et l'on croyait avoir sauvegardé la base. Sur un passage automatique que personne ne lit, c'est une confirmation mensongère. Le durcissement ne vaut que pour l'option : sans elle, rien ne change.
⚠ Le mot de passe MySQL se lit dans IriX.json, là où le nom de la base et le compte l'étaient déjà. Le script en lisait deux sur trois et exigeait le troisième en variable d'environnement — si bien que --with-db ne marchait qu'à la main. Il n'est jamais exporté globalement, seulement passé en préfixe de mysqldump.
⚠ Un seuil d'alerte à 25 Mo. Il n'échoue pas — une grosse archive reste valide — mais il nomme les trois plus gros répertoires. C'est le même raisonnement que le signalement des bancs muets de la 4.16.19 : le vrai correctif d'un défaut silencieux n'est pas le correctif, c'est de rendre le prochain bruyant.
Markdown/BACKUP.md,ROADMAP.md,CHANTIERS.md,ARCHITECTURE.md,INSTALL.md— la nouvelle unité, et les deux défauts.
Avertissements
⚠⚠ Rien n'est encore sauvegardé, et il faut deux gestes pour que ça le soit. D'abord poser la phrase secrète, et la recopier ailleurs : sans elle, les archives sont irrécupérables — y compris celle qui contiendrait le fichier. Un mot de passe ne se pose jamais automatiquement, c'est une règle de ce projet. Ensuite installer l'unité. Les deux commandes sont dans Markdown/BACKUP.md.
Tant que ce n'est pas fait, une perte disque emporte la base, les configurations hors dépôt et Components/Service/anope/conf.
4.16.19 — 11 août 2026
L'outil qui éprouve était le seul que personne n'éprouvait. Le lanceur de bancs annonçait un total faux de 242 constats, et il le faisait depuis des semaines sans que rien ne le signale.
Tools/bancs.sh lit le résumé de chaque banc pour en tirer un compte. Il ne connaissait qu'une famille de format — celle où le nombre précède le mot, « 24 vérifications ». Trois bancs écrivent l'autre, « constats : 18 ok », et ces trois-là valaient zéro :
| Banc | Constats réels | Comptés |
|---|---|---|
Web/tests/test-chanreglages.php | 192 | 0 |
IriX/tests/test-autoconfig-relu.tcl | 32 | 0 |
IriX/tests/test-partyline.tcl | 18 | 0 |
⚠ Le code de sortie, lui, était juste depuis le début. C'est lui qui décide du vert et du rouge, jamais le texte affiché — le défaut ne cachait donc aucune régression, et rien n'était cassé. Il rendait le total invérifiable, et un chiffre invérifiable qu'on recopie dans un document est exactement le travers que ce projet passe son temps à corriger.
Le vrai correctif n'est pas le motif
Élargir la lecture ne règle que les trois cas connus. Ce qui a permis au défaut de durer, c'est qu'un banc compté zéro s'affichait « ok » comme les autres : rien ne distinguait un banc bien lu d'un banc muet. Le lanceur les nomme désormais, à la ligne et dans son résumé final.
⚠ Sans les compter en échec : leur code de sortie est bon, et rougir sur la forme d'un texte punirait le mauvais coupable.
Créations
Web/tests/test-bancs.php— le banc du lanceur, 23 constats et 5 mutations toutes détectées. ⚠ Il exécute la fonction extraite du fichier réel, il ne la réécrit pas en PHP : une copie du motif serait une seconde liste des mêmes règles, et elles divergeraient au premier correctif.
⚠ Son premier jet analysait les bancs eux-mêmes, ligne par ligne, pour vérifier que leur résumé est lisible. Mesuré : deux faux positifs immédiats — test-anope.php écrit le sien dans un ternaire sur trois lignes, et test-protection.php porte une ligne d'échec dont le premier nombre n'est pas le compte. Abandonné. La couverture est tenue à l'exécution, par le signalement ci-dessus, qui ne peut pas accuser à tort.
Modifications
Tools/bancs.sh— les deux familles de format, et le signalement des bancs dont aucun constat n'a été lu.Markdown/CHANTIERS.md,Markdown/ROADMAP.md— revue du 11/08, ci-dessous.
Suppressions
- La table des dix-neuf bancs de
ROADMAP.md. Elle en décrivait dix-neuf quand il y en avait cinquante et un, avec des comptes du 02/08 dont la plupart étaient faux neuf jours plus tard. Elle prévenait elle-même, deux lignes plus haut, qu'elle avait perdu onze entrées et qu'il ne fallait pas la lire. Une liste qui prévient qu'elle ment reste une liste qui ment.
Erreurs
Trois affirmations d'état périmées, trouvées en mesurant plutôt qu'en relisant. Aucune n'était une faute : toutes disaient vrai le jour où elles ont été écrites.
| Où | Ce qui était écrit | Ce qui est mesuré le 11/08 |
|---|---|---|
CHANTIERS.md, point 5 | « l'état d'origine (non) a été remis » pour autolimit | oui sur quatre salons, cochés depuis le panel le 10/08 à 19h25 — sept heures après que la phrase eut été écrite |
CHANTIERS.md, point 3 | chat_hist porte 7 lignes | 30 |
ROADMAP.md, phase 4 | trois manques annoncés | les trois sont comblés — courriel de décision (05/08), webchat, garde-fous du panel |
✅ Et le point 5 y gagne un constat qu'il attendait : « constater qu'un propriétaire coche une case et voit l'effet sur son salon » était sa dernière condition hors autobans. C'est fait — du panel, sur quatre vrais salons, et le journal du bot porte le MODE +l 3 correspondant sur chacun.
Avertissements
⚠ autolimit armé sur #accueil met un +l permanent sur le salon d'accueil du réseau. Il se recalcule à chaque entrée et chaque sortie, donc il ne ferme la porte à personne — mais il ne se recalcule que par IriX. Le bot absent, la dernière valeur reste posée. Ce n'est pas une raison de retirer le réglage, c'en est une de compter ce +l parmi ce qu'une panne d'IriX emporte avec elle.
⚠ ${t}_bans est toujours vide, et c'est le dernier verrou d'autobans : un !newban depuis IRC suffit à le lever.
4.16.18 — 10 août 2026
Une septième case de mode, réservée aux opérateurs du réseau. Signalé par SerVuS : il voulait poser +O sur #!Services! et la page ne le proposait pas.
+O réserve le salon aux opérateurs du réseau. Il avait été écarté des six cases pour une raison exacte : entre les mains d'un propriétaire ordinaire, il rendrait son salon inaccessible à ses membres — et à lui-même, donc sans retour possible.
⚠ Il reste sans paramètre, comme les six autres : c'est ce qui permet de l'offrir en case sans rouvrir la porte que les cases ont fermée — celle d'une chaîne saisie à la main, où un espace glisserait +o quelquun.
| Rôle | Ce qu'il peut poser |
|---|---|
| Opérateur du réseau | les sept modes, +O compris |
| Propriétaire | les six ordinaires |
| ⚠ Rôle vide ou inconnu | les six ordinaires — le défaut est le plus restrictif : une garde qui s'ouvre quand on ne lui dit rien n'est pas une garde |
Trois pièges qu'il a fallu traiter
⚠ La garde n'est pas la page. Une case absente du formulaire n'empêche rien : un POST fabriqué à la main porterait O tout aussi bien. C'est le refus à l'écriture qui tient la porte — et il rejette l'enregistrement entier.
⚠ Un mode qu'on n'a pas le droit de poser est un mode auquel on ne touche pas. Si la chaîne enregistrée avait porté O pour tout le monde, le premier enregistrement d'un propriétaire aurait envoyé -O et défait le mode qu'un opérateur venait de poser, sans que personne l'ait demandé.
⚠ Et le verrou ne doit pas se refermer sur celui qui a posé le mode. Sans le rôle, +O passerait pour un mode inconnu : les cases deviendraient lecture seule, et l'opérateur ne pourrait plus rien régler du tout.
Avertissements
⚠ Une mutation a survécu, et elle était la plus grave : retirer le rôle de l'appel fait à la validation. Tous les constats appelaient la fonction directement, jamais la décision qui s'en sert — ils restaient donc verts pendant qu'un opérateur n'aurait plus jamais pu poser +O. C'est le travers que ce fichier documente déjà pour le verrou des modes, et il a resservi sur la ligne d'à côté.
⚠ Deux constats ont par ailleurs échoué sur du code juste : des variables lues avant leur définition, et une classe [^)]* qui butait sur la parenthèse d'un transtypage.
4.16.17 — 10 août 2026
Le panel au doigt, et quatre pages qui débordaient. Suite de la 4.16.16, après avoir vérifié les quinze pages au lieu des deux déjà regardées.
Le panel était pire que le site
Mesuré sur sa page de connexion : champs à 13 px et 35 px de haut, bouton à 12,5 px et 32 px. C'est le premier contact avec l'administration du réseau, et il se faisait de travers.
⚠ Le panel avait pourtant déjà une règle pour les petits écrans — mais elle ne traitait que la navigation, barre latérale et titre. Une règle qui existe ne couvre que ce qu'elle nomme.
⚠ Pas de !important ici, contrairement au site : ces champs tiennent leurs dimensions de la feuille, pas d'un attribut style. Vérifié avant d'écrire la règle — un !important qui ne sert à rien est une dette, et une mutation veille à ce qu'on ne l'ajoute pas « par symétrie ».
Quatre pages débordaient latéralement
| Cause | Ce qu'elle faisait |
|---|---|
1fr vaut minmax(auto, 1fr) — et auto ne descend jamais sous la largeur minimale du contenu | Une commande dans un <pre> imposait 482 px à une colonne de 323. Les deux écritures se lisent pareil et ne font pas la même chose |
| Un menu déroulant prend la largeur de sa plus longue option | 514 px dans un formulaire de 273, libellé compris |
| Un chemin absolu ne se coupe nulle part | 17 px de débordement pour une seule ligne. ⚠ anywhere et non break-word : lui seul agit sur le calcul de la largeur minimale, qui est ce qui débordait |
Et les petits boutons restaient à 38 px : les boutons pleins étaient déjà à 44, les petits non. ⚠ Sur un doigt, il n'y a pas de petit bouton.
⚠ Ce que la mesure a évité de « corriger »
Le premier relevé accusait la page « Serveurs compatibles » de 494 px — la pire des cinq. Elle est en fait parfaite : son grand tableau défile dans son conteneur, ce qui est le comportement voulu.
Un élément qui dépasse DANS un conteneur qui défile ne fait pas déborder la page. Il a fallu un second relevé — ne retenir un coupable que si aucun ancêtre ne contient le débordement — pour le voir. Sans lui, un tableau qui marche très bien aurait été réécrit.
Vérification finale
Seize pages — les quinze du site plus la connexion du panel, en 375×812 : aucun débordement, aucun zoom, aucun champ ni bouton sous la cible tactile. Et le bureau reste inchangé, la règle visant le pointeur et non une largeur.
4.16.16 — 10 août 2026
Les formulaires du site deviennent utilisables au doigt. Signalé par SerVuS : « je pensais pouvoir cocher les cases depuis mon téléphone, mais c'est pas pratique. »
Deux défauts, mesurés dans un vrai téléphone de 375 px — pas supposés :
⚠ Tous les champs étaient à 14 px, deux à 13,3. iOS zoome de lui-même dès qu'un champ fait moins de 16 px : on touche le champ, la page saute, et il faut dézoomer pour lire la suite. C'est la cause principale de l'inconfort, et elle ne se voit sur aucune capture d'écran — seule une mesure des tailles calculées la montre.
⚠ Les hauteurs tombaient à 39-41 px, sous le minimum tactile de 44. Les boutons, eux, y étaient déjà : d'où une page où l'on vise juste les boutons et de travers les champs.
Les cases à cocher passent de 13 à 22 px — une case de 13 px se rate une fois sur trois. Le libellé qui l'entoure était déjà cliquable ; c'est la case elle-même qu'on visait mal. Et les grilles à deux colonnes s'empilent sous 700 px.
Ce qui n'était PAS cassé
Aucun débordement horizontal, et la grille passait déjà en une colonne. La mesure a autant servi à ne pas corriger ce qui allait bien.
✅ Vérifié dans les deux sens : plus aucun champ sous 16 px ni sous 44 px sur téléphone — et rien n'a bougé sur bureau, où les champs gardent leurs dimensions et la grille ses deux colonnes.
Avertissements
⚠ !important est indispensable ici, et c'est le seul endroit de cette feuille où il l'est : ces champs portent leurs dimensions en attribut style, qui l'emporte sur toute règle externe. Sans lui, la règle est écrasée et ne sert à rien tout en paraissant présente — c'est le genre de correctif qu'on croit acquis parce qu'on le lit dans la feuille. Une mutation le vérifie.
⚠ La règle vise le doigt, pas seulement une largeur. Une tablette au stylet n'a pas le même besoin qu'un téléphone tenu à une main.
4.16.15 — 10 août 2026
Les routes du site public sont enfin croisées avec les pages qu'elles désignent. Le panel avait ce contrôle depuis longtemps ; le site, non — et une route qui désigne un fichier absent ne se voit qu'en cliquant.
Rien n'était cassé : les quatorze routes mènent toutes quelque part, et aucune page n'est inatteignable. C'est la garde qui manquait, pas la cohérence.
Modifications
Web/tests/test-nav-site.php— le croisement, dans les deux sens, plus celui du menu : une entrée qui pointerait vers une route inexistante mènerait le visiteur sur une erreur depuis la barre de navigation, le pire endroit pour en trouver une.
Avertissements
⚠ Deux exceptions sont volontaires, et le banc exige qu'elles le restent. La page d'erreur n'a pas de route — c'est elle qui répond quand aucune ne correspond. Et le chat n'est routé que si le webchat est proposé : sans cela, une installation qui n'a pas ouvert son listener servirait une page annonçant son propre échec. ⚠ Son absence retombe alors sur l'accueil plutôt que sur une erreur, parce que l'adresse a existé et traîne dans les favoris.
⚠ La tolérance accordée au chat est elle-même surveillée. Sans ce garde-fou, elle masquerait une vraie route morte le jour où le chat cesserait d'être conditionnel — une exception qu'on n'éprouve plus devient un trou.
4.16.14 — 10 août 2026
L'accueil du panel montre les Eggdrop posés sur la machine, et l'état de leur console. Demandé par SerVuS.
⚠ C'est une TROISIÈME vérité, et c'est ce qui la justifie. L'onglet « Eggdrop » croise le déclaré et le constaté sur le lien. Ni l'un ni l'autre ne dit ce qui est posé sur le disque : un robot peut être installé et arrêté, installé sans être déclaré, ou déclaré sans avoir jamais été posé. C'est cette lecture-là qui aurait montré d'un coup d'œil, le jour même, qu'un robot venait d'être posé et attendait encore son propriétaire.
Modifications
Web/inc/sys.php— le relevé, et la lecture du port de console.
⚠ L'inventaire vient de systemd, pas du disque. Le site tourne sous un compte qui ne peut pas lire les répertoires des robots posés automatiquement — ils sont créés en 0750. Un relevé fondé sur le disque aurait vu les anciens et manqué tous les nouveaux : le pire des inventaires, celui qui paraît complet.
⚠ Les robots qui attendent leur hello y figurent aussi. Ils tournent dans une unité transitoire, pas encore sous leur unité définitive : ne regarder que celle-ci les aurait tus, et un robot posé puis oublié ne se signale nulle part ailleurs.
Web/pages/admin.php— le tableau. Une console n'est dite ouverte que si le port répond : lelistende la configuration ne dit que l'intention.
Bugs corrigés
| Symptôme | Cause | Portée |
|---|---|---|
| La console d'IriX était annoncée fermée sur un port qui répond très bien | Eggdrop accepte deux formes — listen 3333 et listen 127.0.0.1 3333 —, et le motif lisait le premier nombre venu : il rendait 127 | ⚠ L'écart ne se voit pas au dépôt : la configuration de la machine est ignorée par git, parce qu'elle lui est propre. Lire le fichier versionné aurait donné raison au motif fautif |
Avertissements
⚠ Le banc du panel a rattrapé une classe inventée dans mon propre tableau — badge bad, qui figure déjà parmi les sept noms trouvés le 02/08/2026. Le vocabulaire du panel est clos, et il ne se devine pas.
⚠ Et j'ai d'abord écrit la grille du SITE dans une page du PANEL. Les deux feuilles ont des vocabulaires proches et ne se prêtent rien : c'est exactement le défaut corrigé ce matin, refait dans l'autre sens une heure plus tard.
⚠ Un constat non discriminant laisse survivre une mutation. Celui qui éprouvait listen-addr n'employait que la forme guillemetée du projet — or c'est elle qui bloque tout motif, quel qu'il soit. Le cas qui tranche est l'adresse nue, et il a fallu l'ajouter pour que la porte soit tenue.
4.16.13 — 10 août 2026
Le site et l'installateur annoncent désormais les mêmes serveurs, et le banc l'exige. Vérification demandée par SerVuS : les six serveurs sont-ils bien affichés, marqués, et posables ?
Ils le sont — et la mesure le dit sur chaque source :
| Ce qui a été constaté | |
|---|---|
| La page | les six serveurs nommés, leurs six versions affichées, aucun « théorique » — tous ont vu un lien réel |
| Le compte en tête | « Six serveurs » est dérivé, pas écrit : il ne peut pas mentir |
| Le robot | six dialectes, exactement les six de la page |
| L'installateur | une version et une URL pour chacun ; le serveur maison, lui, vient du dépôt |
⚠ Mais personne ne croisait ces listes. Un serveur annoncé compatible mais absent de l'installateur aurait laissé l'administrateur le compiler à la main sans prévenir ; l'inverse l'aurait privé d'un choix qui marche. C'est le travers des listes recopiées, sur le sujet le plus visible du site.
Modifications
Web/tests/test-compat.php— le croisement, dans les deux sens, plus la concordance des versions. ⚠ Elle n'est exigée que sur les versions précises : la page annonce « 6.x » pour UnrealIRCd, qui est une famille et non un numéro.
⚠ La page dit p10, l'installateur dit ircu : ce sont le protocole et le logiciel qui le parle. La correspondance ne se devine pas — elle est déclarée, et un contrôle exige qu'elle couvre tous les serveurs, de sorte qu'un ajout au site sans entrée fasse rougir le banc.
Avertissements
⚠ Une sous-chaîne n'est pas une preuve, et c'est la troisième fois. Le contrôle du serveur maison cherchait overtchat-hybrid ; la mutation qui renommait le répertoire en overtchat-hybrid-absent y a survécu, puisque le second contient le premier. Même piège que le sélecteur CSS et que la boucle cherchée dans tout un fichier. Ancrer, ou poser une limite de mot.
4.16.12 — 10 août 2026
Une installation neuve ne recevait ni sa version, ni son journal. Trouvé en vérifiant, à la demande de SerVuS, que le projet s'installe encore correctement après les corrections de la journée.
L'installateur ne prend pas le site sur GitHub : il télécharge l'archive que le site fabrique lui-même. Ce que cette archive ne porte pas n'existe pas pour la personne qui installe. Or elle emportait Web, l'exemple d'identité et Tools — et rien de la racine.
| Ce qui manquait | Ce que ça donnait sur une installation neuve |
|---|---|
VERSION | la version installée valait « inconnue », donc le panel ne pouvait plus la comparer à celle publiée : l'exploitant n'apprenait jamais qu'une mise à jour existait |
VERSION.md | la page « Versions » affichait « Journal indisponible » |
Markdown/CHANTIERS.md | le registre des chantiers, rendu en tête de cette page, disparaissait |
Bootstrap/overtchat-setup.sh | le site installé ne pouvait plus distribuer l'installateur à son tour |
⚠ Rien de tout cela ne se voit en développement : le dépôt porte les fichiers, et le site les lit très bien ici. Seule une extraction de l'archife réellement servie le montre.
⚠ Et le contrôle de fraîcheur ne les surveillait pas. Il ne regardait que les répertoires de Web/ : publier une version dans VERSION.md ne régénérait donc pas l'archive, et les installations suivantes recevaient un journal périmé sans que rien ne l'indique.
Modifications
Web/inc/distrib.php— la liste des fichiers de racine que le site emporte, et leur surveillance. ⚠ Elle est close et courte :Markdown/entier n'a rien à partir — ce sont les cahiers de travail du projet, pas des fichiers dont le site a besoin pour fonctionner.
Créations
Web/tests/test-distrib.php— 17 constats. ⚠ Il dérive ce que le site lit hors deWeb/au lieu de recopier une liste : une page qui lira un fichier de plus fera rougir le banc, au lieu d'être découverte par un nouvel administrateur. 6 mutations, 6 détectées.
✅ Vérifié sur une extraction réelle : version, journal, registre et installateur sont là, et aucun fichier sensible ne part.
4.16.11 — 10 août 2026
Les colonnes citées par le code sont désormais croisées avec celles qui existent. C'est le défaut le plus répété du projet : cinq colonnes nommées de mémoire le 07/08, aucune n'existait ; deux de plus le 09/08 sur les tables de l'IA ; une de plus le 10/08 en écrivant un simple contrôle.
⚠ Ce que ça coûte quand personne ne regarde. Une requête qui vise une colonne absente échoue. Côté site, elle remonte et l'on voit. Côté robot, un catch l'avale — le pire cas connu est un nettoyage qui a échoué en silence et laissé une ligne d'essai en production.
Créations
Web/tests/test-colonnes.php— il relève le schéma réel, lit les sources, et croise. Résultat du jour : 299 requêtes examinées sur 54 tables, aucune colonne absente.
⚠ Il renonce plutôt que de deviner : une liste qui porte un décompte, un alias ou une jointure est sautée en entier. Un faux positif coûte plus cher qu'un manque — il fait douter de tout le reste du rapport.
Avertissements
⚠ Sa première version rendait « aucune colonne suspecte » après avoir examiné ZÉRO requête. Le nom de table n'est jamais écrit en clair dans ce projet, et le motif ne reconnaissait donc rien. Le rapport se lisait comme un satisfecit. Un audit qui n'examine rien doit rougir, pas conclure — c'est la même leçon que la mutation qui ne s'applique pas.
⚠ Et le garde-fou compte PAR FAMILLE, pas en total. Une mutation a cassé le seul motif du site : le banc est resté vert, celui du robot fournissant encore assez de requêtes pour satisfaire un seuil global. Une moitié d'audit qui se tait vaut un audit qui ment.
⚠ Une mutation a dû être écartée comme invalide : elle neutralisait le constat final du banc lui-même. Aucun banc ne résiste à ce qu'on le mute pour qu'il mente — ce n'était pas un trou, c'était une mauvaise question.
4.16.10 — 10 août 2026
La pose automatique d'un robot ne pouvait pas aboutir. La première jamais tentée l'a montré en une minute, après des semaines où tout était « écrit et éprouvé ».
L'exécutant lance Eggdrop avec -m puis exige aussitôt le fichier des utilisateurs. Or -m ne crée pas ce fichier : il met le robot en attente. Eggdrop le dit lui-même sur sa sortie :
STARTING BOT IN USERFILE CREATION MODE.
OR go to IRC and type: /msg OverOp-T hello
Le fichier n'est écrit qu'après ce geste. Le script réclamait donc une preuve qui ne pouvait pas encore exister, et rendait « échec » sur une pose parfaitement réussie — répertoire créé, port calculé, robot en écoute.
⚠ Le cahier annonçait pourtant que le robot « naîtra sans son hello ». Personne n'avait vu que l'exécutant, lui, refusait de finir sans — parce que la chaîne n'avait jamais tourné en entier. « Écrit et éprouvé » n'est pas « en service », et vingt-quatre contrôles ne remplacent pas une pose réelle.
Modifications
Tools/overop-pool.php— la pose se fait désormais en deux temps, avec le geste humain au milieu.
Premier passage : le robot est posé, lancé, et la demande passe « en cours » avec ce qu'on attend d'elle. ⚠ Ce qui prouve la pose, c'est que le robot tourne — pas qu'un fichier soit là : un fichier de PID survit à un processus mort.
Passages suivants : dès que le hello a eu lieu, le robot passe sous systemd et la demande est close. ⚠ Tant qu'il n'a pas eu lieu, rien n'est déclaré en échec : il ne s'est rien passé de fautif.
⚠ Et le robot est remis debout s'il est tombé entre deux passages. Il n'est pas encore sous systemd : rien ne le relance, et le hello de l'exploitant tomberait dans le vide sans que personne sache pourquoi.
Les deux autres causes, trouvées aux poses suivantes
Le robot naissait dans le vide. overop.conf désigne tout en relatif — son script, son fichier de PID, son journal. L'exécutant le lançait depuis un autre répertoire : Eggdrop ne trouvait pas son script et refusait de démarrer, en ne laissant qu'un message de langue non résolu. ⚠ L'unité systemd, elle, pose bien un répertoire de travail — c'est pour cela que les robots posés à la main marchaient et que celui du pool ne démarrait pas.
Puis le robot mourait avec le service qui venait de le poser. Le journal donne la cause en deux lignes de la même seconde : « posé, il attend son hello », et « SIGNING OFF ». Le pool est un service à passage unique — en rendant la main, il emporte tout son groupe de processus.
⚠ Et il doit survivre, parce que le hello ne peut pas venir avant. La forme qu'emploie ensuite systemd refuse de démarrer sans le fichier des utilisateurs : « USER FILE NOT FOUND! ». Le cercle est complet — systemd exige le fichier, le fichier naît du hello, le hello exige un robot vivant, et le robot vivant exige de ne pas mourir avec le service.
Le robot part désormais dans sa propre unité, le temps de recevoir son hello. Aucune unité installée n'est touchée, ce que les droits ne permettraient pas de toute façon.
✅ Constaté : le robot est posé, il écoute, et il est sur le réseau.
Avertissements
⚠ Le hello ne s'automatise pas, et ce n'est pas une limite technique. Il désigne un propriétaire. Dans ce projet, les comptes ne se posent jamais tout seuls — la règle vaut ici comme ailleurs.
⚠ Quatre poses ont été nécessaires pour trois causes. Chacune était cachée derrière la précédente : tant que la première bloquait, les deux autres ne pouvaient pas se montrer. Un mécanisme qu'on n'a jamais fait tourner en entier n'a pas un défaut, il en a une pile — et vingt-quatre contrôles au vert n'en disent rien.
4.16.9 — 10 août 2026
Le panel annonçait un port de liaison qui n'existe pas sur ce serveur. Le 7000 était celui d'UnrealIRCd ; Overtchat-Hybrid porte le sien sur 6900 et n'ouvre rien sur 7000. Mesuré ce jour-là : il n'est pas dans les ports en écoute de la machine, quand 6667, 6697, 6900 et 8000 y sont tous.
⚠ Le défaut était connu, corrigé — et à un seul endroit. Le décompte des services l'avait retiré, en expliquant sur dix lignes que le garder comptait éternellement « 3 ports sur 4 » et annonçait un réseau perturbé qui allait très bien. Deux autres endroits ne l'avaient pas suivi : l'accueil du panel le sondait encore, et la page « Serveur » l'affichait comme un service indisponible.
Une correction posée à un seul endroit laisse les autres affirmer le contraire — et ce sont eux que l'exploitant regarde.
Modifications
Web/pages/admin.php,Web/pages/serveur.php— le 7000 cède la place au WebSocket du chat, servi par l'un comme par l'autre serveur. ⚠ Le port est lu dans la constante, jamais recopié : il se règle dans la configuration, et une valeur en dur s'en détacherait au premier changement.
Web/tests/test-statut.php— les trois listes sont désormais croisées, plutôt que la seule qui avait été corrigée en premier. ⚠ Le contrôle ignore les commentaires : les trois fichiers expliquent justement pourquoi ce port a été retiré, et rougir dessus obligerait à effacer l'explication pour faire passer le banc — le pire des remèdes. Une mutation vérifie cette tolérance, en exigeant que le banc reste vert.
Évolutions
Les 28 onglets du panel ont été rendus un par un, hors navigateur, un processus par page. Résultat : aucune notice, aucune page nue, aucun processus tombé. C'est ce rendu qui a mis le port en évidence.
⚠ Le premier passage accusait deux pages à tort. Un set_error_handler est appelé même pour ce que @ supprime : c'est à lui de consulter error_reporting(). La sonde de ports porte son @ depuis longtemps, documenté sur vingt lignes. Le défaut était dans la mesure, pas dans le code mesuré.
4.16.8 — 10 août 2026
Sept endroits du site s'affichaient sans leur mise en forme. Les classes employées existaient — mais dans app.css, la feuille du panel. Le site public ne charge que site.css : ces éléments se rendaient nus, sans erreur, sans une ligne au journal. On ne les voit qu'en regardant.
| Ce qui était nu | Ce que ça donnait |
|---|---|
| La grille de « Serveurs compatibles » | deux cartes empilées sur une seule colonne, au lieu de deux |
| Le tableau des composants d'« Installation » | il débordait au lieu de défiler, sur écran étroit |
| La carte d'avertissement d'ircu | identique aux deux autres — l'avertissement ne se voyait plus |
| Les huit séparateurs de la page privée d'un salon | le trait en relief du navigateur, sur fond sombre |
| Trois boutons du webchat | un fond plein là où ils devaient s'effacer |
| Les colonnes de comptes de « Commandes » | des nombres alignés à gauche |
⚠ Le plus visible est le dernier trouvé : la page où un propriétaire règle son salon en portait huit, et c'est justement celle qu'on allait faire ouvrir. Aucun audit de la page servie ne pouvait les voir — sans session, elle ne rend que son refus. Il a fallu lire le source.
Modifications
Web/public/assets/site.css— quatre règles qui manquaient. ⚠ La couleur de la carte d'avertissement vient de la variable du site, pas de la valeur en dur que le panel emploie.
Web/site/compatibilite.php,installation.php— deux corrigées dans la page, pas dans la feuille : les classes du site existaient déjà sous un autre nom. ⚠ Le vocabulaire CSS du site est CLOS : on n'y ajoute une règle que si rien ne fait déjà l'usage.
Web/site/commandes.php— une classe retirée, pas ajoutée. Au panel, c'est elle qui porte le rembourrage d'une carte ; ici la carte le porte déjà. La définir aurait doublé la marge intérieure. ⚠ Une classe manquante ne se comble pas par réflexe : il faut regarder ce qu'elle ferait.
Créations
Web/tests/test-vues-site.php— 15 constats.test-vues.phpposait ces questions pour le panel ; personne ne les posait pour le site, celui que des visiteurs regardent. Les pages y sont découvertes, jamais énumérées. 8 mutations, 8 détectées.
Avertissements
⚠ Un ?> écrit dans un commentaire // ferme le bloc PHP. Le banc s'est affiché en clair au lieu de s'exécuter, parce qu'un commentaire citait la balise fermante. C'est le cousin exact de l'accolade isolée dans un commentaire Tcl, qui compte pour l'équilibrage. Écrire la balise en toutes lettres.
4.16.7 — 10 août 2026
La limite de places automatique est ouverte au propriétaire d'un salon. Tranché par SerVuS. Le robot garde deux places libres au-dessus du nombre de personnes présentes, et refait le calcul à chaque entrée et à chaque sortie.
C'est un booléen : le propriétaire ne saisit aucun nombre, il est calculé. La surface d'injection est donc nulle — moins encore que les modes de salon, ouverts la veille, qui offrent au moins six lettres à choisir.
✅ Constaté en production, dans les deux sens, sur un vrai salon : la limite se pose au cochage — calculée sur les personnes présentes — et elle repart au décochage. L'état d'origine a été remis.
Bugs corrigés
| Symptôme | Cause | Portée |
|---|---|---|
| Éteindre la limite ne la retirait jamais : le salon restait plafonné à sa dernière valeur, et il aurait fallu un opérateur pour libérer les places | do_autolimit sort aussitôt quand le réglage ne vaut pas oui. Rien n'envoyait le -l, et le panel n'écrit qu'en base : il ne parle pas au robot | Le réglage aurait été un piège. C'est le travers déjà corrigé pour les modes de salon — décocher doit retirer vraiment — retrouvé ici par l'autre porte |
| Cocher ne posait rien tant que le robot n'avait pas encore lu le salon | La pose exigeait un changement de valeur. Sur un cache vide, les deux lectures rendent la même chose : aucune transition n'est vue | Trouvé en éprouvant pour de vrai, pas en relisant. Le propriétaire aurait coché et n'aurait rien vu — exactement ce qu'on cherchait à éviter |
⚠ Trouvé en relisant le code AVANT d'ouvrir, pas après. Une case qui s'arme mais ne se désarme pas est pire qu'une case absente : le propriétaire croit avoir tout remis à zéro.
Modifications
IriX/cmds/IriX-sql.tcl—undo_autolimit, et les deux chemins qui y mènent. Celui de la commande!autoset, et surtout celui du panel : le robot relit sa configuration toutes les 30 secondes, et c'est là qu'il faut voir qu'un réglage vient de s'éteindre. ⚠ L'ancienne valeur ne se lit qu'avant de vider le cache — après, il n'y a plus que la nouvelle. ⚠ Un cache déjà vide rend la nouvelle valeur : le pire cas est donc un retrait manqué, jamais un retrait intempestif.
⚠ Et la POSE suit le même chemin, pour la même raison. Le calcul n'était branché qu'à l'entrée et à la sortie d'une personne : cocher la case ne faisait donc rien de visible tant que le salon ne bougeait pas. Le propriétaire regardait, ne voyait aucune limite, et concluait que le réglage ne marchait pas. Un réglage sans effet visible est un réglage qu'on croit cassé — et c'est ce qu'on allait faire constater.
⚠ La pose ne regarde que la NOUVELLE valeur, pas la transition — et c'est l'essai réel qui l'a imposé. Exiger un changement la faisait manquer chaque fois que le robot n'avait pas encore lu le salon : les deux lectures rendent alors la même valeur, on lit « oui → oui », et rien ne part. Le retrait, lui, garde la transition : sans elle, il enverrait un -l à chaque relecture de tout salon non réglé.
Web/inc/chanreglages.php— ⚠ la QUATRIÈME liste des mêmes clés a disparu. Après la liste close, les libellés et les défauts, la requête de lecture énumérait elle aussi ses colonnes à la main. Ouvrir un réglage sans qu'elle suive l'aurait rendu invisible : la page aurait affiché éternellement sa valeur par défaut pendant que la base disaitoui. C'est le défaut du 09/08/2026, à un fichier près — et il attendait dans la ligne d'à côté. Les colonnes sont désormais dérivées.
Web/site/salonadmin.php— la section « Les places de ce salon », et ce qu'elle dit au propriétaire : le nombre est calculé, et remettre sur non retire la limite.
Évolutions
⚠ Le banc de relecture éprouvait une COPIE. Il portait scan_config « recopiée telle quelle », pour ne pas charger 11 800 lignes qui exigent un Eggdrop. Une copie ne reste juste que le temps où personne ne touche à l'original : la procédure a gagné le retrait de la limite, la copie ne l'avait pas, et le banc serait resté vert en éprouvant du code qui n'est plus servi. Il découpe maintenant la vraie procédure dans le fichier — info complete en trouve la fin, c'est Tcl qui compte les accolades.
Deux croisements de plus, chacun contre un maillon qui manquait : un réglage ouvert doit avoir un champ de saisie dans la page, et la requête de lecture doit dériver de la liste close.
Avertissements
⚠ autobans reste fermé, et pas pour la même raison. Ce n'est pas le réglage qui gêne : c'est que la table des bans est vide. L'ouvrir aujourd'hui offrirait une case sans le moindre effet — exactement le travers corrigé pour les grades. Il attend qu'un opérateur pose un premier ban.
⚠ Trois mutations ont d'abord SURVÉCU, et chacune disait vrai. Un contrôle cherchait la clé n'importe où dans la page : le libellé suffisait à le satisfaire alors que la saisie avait disparu. Un autre cherchait la boucle dans tout le fichier, où une seconde boucle identique répondait pour la première — le piège du sélecteur CSS, à un fichier près. Le troisième venait du banc : son simulateur rendait l'ancienne valeur quel que soit l'endroit d'où on l'appelait, donc l'ordre des deux gestes lui était indifférent. Les trois contrôles ont été refaits, et les neuf mutations sont détectées.
4.16.6 — 10 août 2026
Cette page affichait ses propres symboles de mise en forme. Quarante-six passages du journal montraient des astérisques au lieu d'être en italique, dix accents graves traînaient au milieu du texte, un tableau portait une colonne fantôme, et la forme des ordres échangés entre robots se lisait à plat, ordre et accusé mêlés sur une seule ligne.
Le moteur de rendu du site est volontairement minimal : plutôt qu'une bibliothèque entière pour afficher deux fichiers, il ne couvre que la syntaxe que ces fichiers emploient. C'est un bon calcul — à condition qu'il la couvre vraiment. Il en manquait quatre morceaux, et ils s'accumulaient à mesure que le journal grandissait.
Modifications
Web/inc/markdown.php— quatre manques comblés.
L'italique n'était pas connu : « un compte » s'affichait avec ses astérisques. ⚠ Il ne suffit pas d'apparier deux astérisques : 867 lignes du journal en portent un, et l'immense majorité ne sont pas de l'italique mais des jokers de noms de fichiers — ui_*, overop@*. Les apparier au hasard ferait disparaître tout ce qui les sépare. Les deux astérisques doivent donc toucher du texte.
Les blocs de code n'étaient pas connus. ⚠ Ils se traitent avant le saut des lignes vides, sinon un bloc qui en contient voit ses lignes se coller les unes aux autres.
Le double accent grave — la façon de citer un accent grave littéral — s'appariait de travers et en laissait quatre visibles dans une cellule.
Enfin, le code cité est désormais mis à l'abri des autres règles au lieu d'être seulement traité en premier. ⚠ La nuance a son importance : une fois la balise écrite, les règles suivantes travaillaient encore sur son contenu, et un astérisque cité dans une commande ouvrait un italique.
Web/site/versions.php— le style des balises neuves. ⚠ Le<code>d'un bloc héritait sinon du cadre du code intra-ligne : une bordure par ligne, et un fond dans un fond.
Le sommaire affichait par ailleurs le titre brut : un titre citant un chemin y montrait ses accents graves, alors que le titre lui-même, plus bas dans la page, était rendu correctement. ⚠ Il est désormais dépouillé en passant par le rendu, et non en énumérant les syntaxes à retirer : une seconde liste se périmerait à chaque syntaxe ajoutée au moteur.
Web/inc/site.php— le menu « Installer » ne reste plus déplié. On choisissait « Serveurs compatibles », la page s'affichait, et le panneau restait ouvert par-dessus le haut du contenu. Il portaitopendès que la page courante appartenait au groupe.
⚠ Le repère de position n'est pas perdu, et c'est ce qui permettait de le retirer : le groupe reste marqué active, et le style le colore déjà en couleur d'accent — fermé ou non. Le commentaire du code défendait open par la crainte inverse ; la feuille de style y répondait déjà.
Créations
Web/tests/test-nav-site.php— 21 contrôles sur la barre du site public, qu'aucun banc ne couvrait. ⚠ L'un d'eux exige la règle de style du groupe actif : si elle disparaissait, refermer le menu deviendrait une vraie perte d'information, et plus personne ne saurait pourquoi.
Bugs corrigés
| Symptôme | Cause | Portée |
|---|---|---|
| Un tableau du journal affichait quatre colonnes sous un en-tête qui en compte trois, et deux accents graves flottaient au milieu | Le découpage des cellules coupait sur tout tube, y compris celui cité dans du code — `base\|<insulte>` | Le même défaut que celui corrigé la veille pour le tube échappé, mais par l'autre porte : ici, aucune faute d'écriture n'était visible dans le fichier |
Avertissements
⚠ Le banc était vert, et il l'est resté pendant que la page affichait tout cela. Il éprouvait le moteur sur les syntaxes qu'il connaissait déjà, pas sur ce que les fichiers servis contiennent réellement. Les contrôles ajoutés portent désormais sur le vrai contenu : aucun accent grave, aucun astérisque, aucun tilde ne doit rester visible hors du code, et chaque ligne de tableau doit compter autant de cellules que son en-tête.
⚠ Un contrôle trop large est aussi un défaut. Celui qui interdisait les tildes est passé au rouge le jour où le code a été mis à l'abri — c'est-à-dire le jour où le moteur a cessé de mettre en forme ce qu'un auteur cite volontairement. Le journal écrit `~~texte~~` pour décrire le barré : l'y laisser est le bon rendu.
4.16.5 — 9 août 2026
Le filtre de modération coupait tous les mots accentués en deux. Il découpait « découvre » en « couvre », « réseau » en « seau », « agréable » en « agr » et « able ». Un filtre censé reconnaître des propos déplacés en français, qui ne voyait jamais un mot accentué en entier.
Le défaut a été trouvé le jour où le réseau lui a soumis ses premiers vrais messages : personne ne l'aurait vu autrement. Il ne se lit pas dans le code — la règle en cause paraît raisonnable — et il n'apparaît qu'en regardant les mots réellement appris.
Créations
IriX/tests/test-ia-tokens.tcl— 28 contrôles sur le découpage des mots, et 4 essais de sabotage, tous détectés. Cette partie du filtre n'était éprouvée par aucun banc : c'est ainsi que le défaut a tenu.
Modifications
IriX/cmds/IriX-sql-IA.tcl— les accents sont désormais ramenés à leur lettre simple avant le découpage. « agréable » et « agreable » donnent le même mot appris, ce qui est voulu : les gens écrivent sans accents, et deux entrées pour un mot diviseraient son compte par deux.
Bugs corrigés
- Le premier vrai trafic a été mesuré de bout en bout, et la chaîne tient : le filtre reçoit les messages, les juge, et apprend enfin ses premiers mots. Le blocage annoncé depuis trois semaines est levé.
- L'historique des salons s'écrit également — sept messages, sept lignes gardées.
Avertissements
- ⚠ Le vocabulaire appris pendant ces essais a été effacé, deux fois. Le filtre doit apprendre de vrais visiteurs, pas de messages écrits pour le tester. Le compteur est donc remis à zéro volontairement : ce n'est pas une panne, c'est l'état voulu tant que personne n'est venu parler.
- ⚠ Les alphabets non latins restent découpés. Limite assumée : le réseau est francophone.
Erreurs
- Trois essais de sabotage ont été rapportés « survécus » alors qu'ils ne s'étaient pas appliqués du tout. C'est la deuxième fois dans la journée. L'outil vérifie désormais qu'il a bien modifié le fichier, et s'arrête en erreur sinon.
4.16.4 — 9 août 2026
Le chat effaçait votre conversation en se reconnectant. Quand la connexion tombait, il affichait « Connexion interrompue par le réseau — rien de ce que vous avez écrit n'est perdu », puis se rebranchait deux secondes plus tard en vidant l'écran. Le message se démentait lui-même, et le visiteur perdait le fil de ce qu'il lisait pour une coupure dont il n'était pas la cause.
Modifications
Web/public/assets/chat.js— une reconnexion n'est plus traitée comme une connexion neuve. Les lignes déjà affichées, les onglets et les conversations privées restent. Ne repart que ce qui est devenu faux : la liste des présents, le sujet et les modes, que le serveur redonne aussitôt.- Le chat dit qu'il est revenu — « Reconnecté » — et rappelle du même coup que l'on n'est plus identifié. Sans cette ligne, l'avertissement de coupure restait le dernier message affiché et l'on croyait la panne en cours.
- Le visiteur n'est plus ramené de force sur le salon d'origine : s'il lisait une conversation privée ou un autre onglet, il y reste.
Évolutions
- La reconnexion automatique a été constatée dans un vrai navigateur, ce qu'aucun banc ne peut faire : il faut une page, une vraie connexion et une vraie coupure. Coupure à 13 h 47 min 01 s, reconnexion à 13 h 47 min 03 s — les deux secondes annoncées. Les deux salons ouverts ont été retrouvés, et le serveur a bien vu revenir la connexion.
- La garde de l'historique a été constatée du même coup : sans identification, le chat affiche « Identifiez-vous pour relire l'historique de ce salon ».
Erreurs
- En lisant le code, un second défaut avait été déduit : la liste des salons à retrouver paraissait effacée avant d'être utilisée. La mesure a montré le contraire — les deux salons revenaient. La ligne incriminée appartenait à une autre fonction. Une lecture rapide ne vaut pas une mesure, y compris quand elle accuse.
4.16.3 — 9 août 2026
Le message d'accueil du serveur pouvait promettre une protection retirée. À la connexion, il annonce que les messages privés traversent un filtre automatique. C'est vrai — sauf si un opérateur venait de couper ce filtre par une commande tapée dans son client. Le message, lui, continuait de l'affirmer, avec l'autorité que donne un texte officiel.
C'est une promesse de confidentialité. Quelqu'un peut lui faire confiance pour décider d'écrire, ou non, un message privé.
Créations
Config/systemd/overtchat-motd.{service,timer}— le message d'accueil est confronté à l'état réel du filtre toutes les cinq minutes, quel que soit le chemin par lequel cet état a changé.
Bugs corrigés
- Un troisième chemin échappait à tout. Le message était déjà réécrit au démarrage du serveur et après un rechargement demandé au système — les deux seuls moments prévus. Mais un opérateur peut recharger la configuration depuis son client, et ce chemin-là ne prévenait personne. Le même défaut avait été corrigé le 2 août pour l'autre porte ; celle-ci était restée ouverte.
Évolutions
- On constate désormais l'état plutôt que d'espérer avoir intercepté tous les événements qui le changent. C'est le raisonnement déjà retenu pour les opérateurs du panel : un passage régulier ferme aussi les chemins qu'on n'a pas prévus.
- Rien n'est réécrit sans raison : le générateur compare le texte composé à celui en place et ne touche à rien s'ils sont identiques. Un passage coûte un huitième de seconde.
- Le banc du message d'accueil passe à 43 contrôles et 6 essais de sabotage, tous détectés. Le rattrapage a été prouvé de bout en bout : le fichier a été volontairement remplacé par une ligne d'essai, puis retrouvé identique après un passage.
- Un tube dans un tableau ne coupe plus la case en deux. Écrire une commande dans un tableau de la page Versions faisait apparaître une colonne fantôme, dont le titre était un bout de la commande. Découvert en écrivant la ligne d'installation ci-dessus — elle s'affichait de travers.
Erreurs
- La feuille de route annonçait ce défaut comme entier. Il était corrigé à moitié depuis une semaine, et personne n'avait mis la case à jour. C'est en allant vérifier la case qu'on a trouvé la porte encore ouverte.
- Un essai de sabotage annoncé « survécu » ne l'avait pas été : le sabotage ne s'était pas appliqué du tout, et le rapport donnait ce silence pour un verdict. Un essai qui n'a pas eu lieu n'est pas un essai réussi.
Avertissements
- ⚠ L'unité reste à installer : cela demande des droits que l'automatisme n'a pas. Tant qu'elle ne tourne pas, le message d'accueil dépend toujours des deux seuls chemins précédents.
4.16.2 — 9 août 2026
Le propriétaire d'un salon choisit désormais les modes de son salon, dans l'onglet Réglages : messages de l'extérieur, sujet réservé aux opérateurs, salon modéré, salon discret, réservé aux pseudonymes enregistrés, actions refusées. Six cases à cocher, et le robot applique.
Ce réglage était gardé fermé pour une raison précise, et c'est la saisie qui posait le problème, pas le réglage. Le robot transmet la ligne telle quelle au serveur : un simple espace y aurait glissé un ordre tout autre, et quelqu'un se serait donné des pouvoirs par le robot du réseau. Des cases suppriment cette possibilité — il n'y a plus rien à écrire.
Modifications
Web/inc/chanreglages.php— les modes du salon rejoignent la liste des réglages ouverts, sous une forme qui n'accepte que des cases. Décocher retire vraiment le mode : l'enregistrement dit au robot ce qu'il faut poser et ce qu'il faut enlever, en une fois. Un salon dont les modes ont été posés par un opérateur reste verrouillé, et la page le dit.Web/site/salonadmin.php— les six cases, ce que chacune fait en clair, et l'avertissement quand elles sont en lecture seule.Markdown/WEB.md— le passage qui affirmait ces réglages fermés était faux depuis le jour même ; il est corrigé et détaille les précautions.
Bugs corrigés
- Un défaut trouvé dans le banc, pas dans le code. La garde qui empêche d'effacer les modes posés par un opérateur était écrite au milieu de la fonction d'enregistrement. La neutraliser laissait le banc vert : les contrôles portaient sur la fonction qui détecte, jamais sur la décision qui l'écoute. La décision a été sortie à part, et un contrôle vérifie maintenant qu'elle est bien appelée — pas seulement écrite.
Évolutions
- Le banc des réglages de salon passe à 144 contrôles et 8 essais de sabotage, tous détectés.
Avertissements
- ⚠ Personne n'a encore coché une case sur un vrai salon. Le banc prouve la mécanique ; il ne peut pas prouver qu'un propriétaire voit le mode apparaître. Cela demande un navigateur et un salon réel.
4.16.1 — 9 août 2026
La page qui affiche les carnets ne savait pas les lire. Le moteur Markdown du site ignorait deux syntaxes que les deux fichiers publics emploient. Les chevrons des citations s'affichaient en clair — jusqu'au milieu d'une phrase quand la citation faisait deux lignes — et les tildes du texte barré restaient là où un point périmé aurait dû être rayé. Le défaut était visible par n'importe quel visiteur de la page Versions.
Créations
Web/tests/test-markdown.php— 39 constats sur le moteur de rendu, et 5 mutations qui prouvent qu'il rougit. Il éprouve aussi les vrais fichiers servis par la page, pas seulement des cas d'école : ce sont eux qui ont révélé le défaut.
Modifications
Web/inc/markdown.php— les citations>deviennent des<blockquote>. Leur contenu est rendu par un appel récursif, sans quoi une citation perdrait ses listes, ses tableaux et son gras. Le sommaire du bloc est jeté : un titre écrit dans une citation n'a pas à figurer dans la table des matières de la page. Le barré~~texte~~devient<del>.Web/site/versions.php— le style des deux balises neuves. Le vocabulaire CSS du site est clos ; une balise ajoutée au moteur sans style s'affiche au défaut du navigateur, c'est-à-dire de travers.Markdown/CHANTIERS.md— seconde revue du 9 août.
Bugs corrigés
- Les citations n'étaient pas rendues du tout. 11 lignes citées dans le registre des chantiers, 17 dans ce journal : leur chevron partait dans le texte affiché. Un paragraphe suivi d'une citation avalait la citation entière.
- 12 tildes de texte barré restaient visibles sur les deux fichiers.
Erreurs
- Le premier constat sur le style de la page cherchait
.md blockquotesans son accolade. Or la sous-règle.md blockquote p:first-childcontient déjà cette chaîne : le constat restait vert alors que la règle principale avait disparu. C'est la mutation qui l'a attrapé, pas la relecture.
Avertissements
- Le registre des chantiers a livré un sixième point périmé : l'historique de modération d'un salon existait déjà, et affichait déjà. Le registre visait la table
IriX_modq, qui est bien vide — mais l'écran ne lit pas cette table.
4.16.0 — 9 août 2026
Cinq chantiers passés dans l'ordre, et trois d'entre eux disaient faux. Le registre des chantiers annonçait des manques qui n'existaient plus : les messages privés du chat étaient faits depuis des semaines, les garde-fous de l'installation automatique étaient écrits, et le rôle qui « masquait » la propriété a été corrigé plutôt que documenté. Ce registre ne se contente donc pas de trop dire — il sous-dit aussi, et il se mesure comme on mesure l'état d'un serveur.
Créations
Tools/overop-pool.php+Config/systemd/overtchat-botpool.{service,timer}— l'exécutant du prêt de robot. Le bot demandait dans le vide : il déposait ses demandes dans une file que personne ne lisait. Ce service la lit et pose le robot — répertoire, configuration, démarrage. Un seul par passage, le minuteur repasse toutes les cinq minutes : poser toute la file d'un coup ferait d'un compteur emballé une flotte de robots en une seconde.Web/inc/chathist.php,Web/sql/020-chat-hist.sql,Web/tests/test-chathist.php— l'historique des salons du chat. En rejoignant un salon, on voit ce qui s'y est dit avant d'arriver. Gardé trente jours, puis effacé.IriX/tests/test-ia-amorce.tcletWeb/tests/test-botpool.php— deux bancs d'essai pour les deux points les plus délicats du jour.
Évolutions
- Le webchat se rebranche tout seul après une coupure réseau. Cinq tentatives espacées de 2 à 30 secondes, puis abandon annoncé — un silence laisserait croire que ça essaie encore. Les salons rejoints sont retrouvés. ⚠ Sans ré-identification : le mot de passe ne survit pas à la connexion qui l'a reçu, et cela reste vrai.
- L'historique se relit par compte, jamais anonymement. L'identification est exigée partout, même sur un salon ouvert à tous : un historique rendu à quiconque passe deviendrait une archive du réseau, relisible sans laisser de nom. Sur un salon réservé, être identifié ne suffit pas — il faut y avoir un rôle, sans quoi la réserve ne vaudrait que pour le direct.
- Les robots de prêt annoncent ce qu'ils servent (
BOTSTAT, toutes les cinq minutes). Le bot déduisait leur charge de sa seule base ; un fichier divergent la laissait affirmer qu'un robot tient des salons qu'il a quittés. Seul l'écart laisse une trace : trois robots bavards écriraient 864 lignes par jour pour ne rien dire. - Le propriétaire d'un salon le reste, même opérateur du réseau. Il ne voyait jamais le bouton « Demander un robot » de son propre salon.
- Le propriétaire arme lui-même les grades de son salon. Ils étaient posables partout et ne s'appliquaient nulle part — un réglage décoratif.
Bugs corrigés
- La modération automatique ne pouvait pas commencer à apprendre. Sans vocabulaire connu, elle jugeait chaque message « pas assez sûr » et s'arrêtait là — sans jamais rien retenir. Elle apprend désormais des messages que le filtre de mots vient de laisser passer, et s'arrête d'elle-même dès qu'elle en sait assez.
- Un réglage de salon ne demande aucun redémarrage : le bot relit tout seul en trois secondes. La consigne inverse coûtait 75 secondes de coupure à chaque fois, pour rien.
- Une purge d'historique écrite mais jamais appelée. Le code était juste et ne tournait pas ; la table aurait grossi sans fin.
Avertissements
- ⚠ L'apprentissage de la modération n'a encore rien appris : il attend une vraie conversation sur un salon surveillé. Tant que personne ne parle, ce zéro ne prouve rien — ni que la correction marche, ni qu'elle échoue.
- ⚠ L'installation automatique n'a jamais posé de vrai robot. Le service tourne, la file est vide, il n'a donc rien eu à faire.
- ⚠ La reconnexion du chat et l'affichage de l'historique restent à constater dans un vrai navigateur : aucun banc ne peut les éprouver.
- ⚠ Les conversations privées ne sont pas dans l'historique, et n'y seront pas : le serveur ne les transmet à personne d'autre.
4.15 1 version · 7 août 2026 5 créations 2 évolutions 3 bugs corrigés 1 suppression 4 erreurs 4 avertissements
4.15.0 — 7 août 2026
Ce que les documents affirmaient, et ce que la mesure a dit. Onze lots ce jour-là. Six ont produit du code ; cinq n'ont produit qu'un constat — le défaut annoncé n'existait plus, ou pas comme il était décrit. PROJET.md, ROADMAP.md et IRIX.md portaient un écart de niveau sur !banall depuis trois semaines, et une décision d'exploitation attendait dessus pour rien. Le carnet donnait deux tables pour mortes alors qu'elles sont conservées à dessein et attendues au ROADMAP. Les supprimer aurait détruit du travail en cours.
Créations
IriX/cmds/irix-partyline.tcl— la console DCC d'IriX. Elle n'écrit aucune commande : elle EXPOSE les quatre-vingts existantes.putnoticesait désormais écrire vers une console quand::soc::dcc_idxest posé, un pont pose ce contexte, adapte la signature{hand idx text}→{nick host hand chan text}, et le RETIRE — y compris quand la commande lève. Le salon, qu'une console n'a pas, se donne en premier mot. Trois commandes s'y ajoutent que l'IRC ne peut pas donner :.bots(déclaré contre constaté),.compte(base SQL contre userfile Eggdrop),.lien(état du botnet).Web/inc/chanreglages.phpetWeb/pages/eggdrop.php— le lot 3 du prêt de robot, et la page des Eggdrop au panel.users.auth_uid— quelle CONNEXION s'est identifiée.OverOp-QetOverOp-S, décidés au §14 dePRET-ROBOT.mdet jamais posés jusqu'ici : répertoires, configurations, ports 3342 et 3343, déclarés au registre, observés par le bot, et sous systemd. Le parc annonce enfin trois robots et soixante places.- Bancs :
test-partyline.tcl(18 constats),test-autoconfig-relu.tcl(13),test-chanreglages.php(68).test-auth.tclpasse de 29 à 43.
Suppressions
- Le mode
+G— dix emplacements. Déclaré, posable, annoncé en 004, 005 et PROTOCTL, et lu par personne : aucunif (… & CMODE_CENSOR)n'existait, et aucune liste de mots côté serveur. La modération réelle passe parantimotd'IriX, qui ne consulte pas ce mode. ⚠ Deux des dix emplacements étaient des chaînesCHANMODESécrites en dur — ungrepsur+Gne pouvait pas les voir ; c'estVERSION.mdqui a mis sur leur piste, en précisant « annoncés en 005 ». Les bits libérés ne sont pas réattribués.
Évolutions
- L'identification appartient à une connexion, plus à un pseudonyme.
auth_uidretient l'UID identifié ;::soc::uid_du_lienle cherche dans le registre du dispatcher. Trois précautions, chacune contre le défaut inverse : l'écriture d'auth_uidest séparée de celle d'auth(groupées, unUPDATEsur base non migrée échouerait en entier et lecatchl'avalerait) ; unauth_uidvide fait retomber sur le test par pseudonyme ; la révocation efface aussi l'UID. IriX-sql-fixes.tcldevientIriX-svc.tcl(A-14). Il ne portait plus une seule rustine : 351 lignes, huit procédures::soc::svc::, dontscan_actions, le cycle de 30 s qui sert le panel.
Bugs corrigés
- Les écrans d'aide doublaient les sanctions dans la file. Trois rafales — 8, 24 et 7 lignes — sortaient par
putspeed_x, c'est-à-direputquick, la file HAUTE priorité, sur un bot dont le métier est de sanctionner vite. Les 42NOTICEqui contournaientputnoticey reviennent ;putnoticesen envoie N en un appel. - Un réglage posé depuis le site restait sans effet.
::soc::auto::cachen'était vidé que par!autoset: la page de salon écrivait en base, et le bot gardait l'ancienne valeur jusqu'au rechargement de ses scripts.::soc::auto::scan_configcompareupdated_atau cycle et relit — le premier passage NOTE sans invalider, sinon le cache se viderait à chaque démarrage. - Reprendre un pseudonyme dans les 4 s donnait l'identification du partant. Le différé de révocation testait le nom ; il suit maintenant la connexion.
Erreurs
Constatées dans la documentation, pas dans le code.
- L'écart
!banall/!unbanall/!kickalln'a jamais existé. Trois documents affirmaient « déclarésircops, codésadmin».IriX-table.tclles déclare niveau 6, et le niveau 6 ESTadmin—ircopsvaut 10. Et${t}_commandsn'est de toute façon jamais lue : le contrôle d'accès est en dur dans le Tcl. Une déclaration que rien ne consulte ne peut pas être « en écart » avec le code. - Le défaut 17 est clos : le SID annoncé vient bien de
server_sid. Constaté sur le lien en service —server_sidvaut003, les cinq pseudo-clients portent003AAAAAAà003AAAAAE, quand les clients ordinaires portent10A…, le SID du serveur. - Le carnet donnait pour manquants le template systemd d'
overop@et l'écrivain debotreg_pooshy.seen_at: les deux existaient déjà. - Le
sedde pose d'OverOp-Havait réécrit sa propre documentation ens/OverOp-H/OverOp-H/g. La pose de Q et S vise les lignes de réglage.
Avertissements
- ⚠
ia_tokensvaut 0, et ce n'est pas une panne : c'est un cercle vicieux. Sans token,::soc::ia::scorerend{0.0 0}; le score passe sous le seuil, et la brancheelsen'entraîne que si$sig >= $min_tokens— or$sigvaut 0. Le modèle ne peut pas démarrer seul. Sa seule amorce esttrain_from_sanction, appelée quand le filtre de mots sanctionne — chaîne vérifiée complète. Elle n'avait rien à apprendre parce qu'antimotn'était actif que sur#Csadmin, salon d'administration. Il l'est désormais sur#accueilet#membres. ⚠ Ne pas injecter de jetons d'essai : du vocabulaire inventé fausserait durablement tous les scores. - ⚠ Le rôle
ircopmasque le rôleproprietaire. Un opérateur réseau, même propriétaire d'un salon, ne verra jamais le bouton « Demander un robot », qui exigerole === 'proprietaire'. Le chemin a été éprouvé avec un compte d'essai non-opérateur ; il fonctionne, mais il reste invisible à qui est IRCop. - ⚠ Un
restart irixmet 75 secondes à ramener les neuf connectés, pas 25. Le lien S2S doit se refaire ET réintroduire les cinq pseudo-clients. - ⚠
::soc::logécrit dansIriX-sql.log, pas dansjournalctl. Chercher une trace::soc::logparjournalctlne rend rien — et ce vide se lit comme « le mécanisme ne tourne pas ». Ce seul piège a produit trois conclusions fausses le même jour, dont un recul sur du code qui fonctionnait.
4.14 1 version · 5 août 2026 1 création 5 avertissements
4.14.0 — 5 août 2026
Du code qui affirme, et rien qui exécute. Sept défauts fermés ce jour-là, et ils sont tous de la même famille : une colonne sans écrivain, un verbe jamais routé, un appel à une procédure qui n'existe pas, un commentaire qui décrit un effet absent. Aucun ne se signalait, et les bancs passaient — ils éprouvent des procédures, pas des chaînes, et ces trous étaient tous aux jointures, là où un module suppose qu'un autre fait le travail.
Le seul moyen de les voir : un essai qui traverse tous les modules. C'est désormais écrit en tête de PRET-ROBOT.md.
Le panel garde trace des avertissements d'expiration
Tools/alerte-pseudo.php chargeait config.php et mailer.php, jamais db.php. Or mail_log() s'ouvre sur une garde function_exists('qx'), prévue pour le cas « table absente » — elle avalait aussi « base jamais chargée ».
Le courriel partait, l'outil rendait 0, le bot posait expire_alerte, et le panel n'en gardait aucune trace. Le jour où un titulaire dit n'avoir pas été prévenu, c'est cette ligne qu'on cherche.
Invisible autrement qu'en provoquant l'envoi : status=sent dans journalctl -u postfix, et rien dans l'onglet Courriel.
!nickexpire — le balayage se déclenche à la demande
Seul ::soc::scan::daily appelait ::soc::nickexpire::balayage, une fois par jour : le maillon exec php depuis Eggdrop ne pouvait pas être éprouvé. !iapurge a sa commande manuelle pour la même raison.
Chaîne prouvée de bout en bout à la seconde près — commande IRC → balayage → exec php → Postfix → status=sent → expire_alerte posée.
⚠ Relevé au passage : IriX_users ne contient qu'un seul compte, sans adresse. L'expiration n'a aucun sujet aujourd'hui — ce n'est pas une panne.
Prêt de robot — quatre maillons manquaient entre les modules
Le cahier affichait ✅ sur tous les lots, à juste titre : le code était écrit, les bancs passaient, l'essai REOP du 03/08 avait réussi. Et rien ne fonctionnait de bout en bout.
- le robot ne savait pas ENTRER :
overop.tclne comprenait quePART, et les deux listes closesORDRESne portaient que ce verbe. Un mandat accordait un droit que rien ne pouvait exécuter — il fallait éditer le chanfile à froid ; opmybotn'inscrivait qu'IriX :_sync_presencelit$::botnicket testebotonchan, soit « moi, suis-je ici ». La table disait « IriX est sur ce salon » quand son nom promet « qui siège vraiment » ;botreg_pooshy.seen_atn'avait aucun écrivain :staterestaitunknownà vie, alors que le bandeau « OpMyBot Offline » du site en dépend ;- l'accord d'un mandat n'était pas journalisé, malgré le « TOUJOURS appelée » en tête de
journaliser.
⚠ ::overop::entrer pose le salon dans la liste du robot au lieu d'émettre un JOIN : Eggdrop rejoint de lui-même un salon actif, et le refait après un netsplit. Un JOIN sec entrerait une fois.
⚠ Ajouter un verbe, c'est toucher DEUX listes — ::overop::ORDRES et ::soc::botreg::ORDRES. L'oubli de la seconde a été rattrapé par le banc du robot, pas par une relecture.
IriX_opmybot accepte plusieurs bots par salon (migration 018)
Défaut introduit le jour même en écrivant _sync_robots : la table portait UNIQUE (channel), vrai tant que seule _sync_presence écrivait. Dès que les robots prêtés s'y sont ajoutés, les deux écrivains se sont disputé la ligne — #accueil a perdu IriX, et added_by a menti.
⚠ Sans contradiction avec le §14 du cahier (« un salon, un robot ») : cette règle porte sur IriX_mandat, le droit. Ici c'est la présence constatée, et deux bots peuvent siéger.
L'adresse du visiteur ne se déclare plus elle-même
Faille réelle. Deux fonctions lisaient X-Forwarded-For : contact_ip() n'y croyait que depuis le relais local, reg_client_ip() sans condition. L'en-tête est trivial à forger : un visiteur d'Internet pouvait s'attribuer l'adresse de son choix et contourner les plafonds d'inscription en la changeant à chaque essai, le panel étant exposé publiquement.
client_ip() (inc/http.php) porte désormais la règle seule. C'est le défaut des listes recopiées appliqué à un contrôle de sécurité : la règle était juste à un endroit, perdue en route dans la copie.
Le panel exposé : ses deux garde-fous
ROADMAP.md les qualifiait de « manques réels et non plus théoriques » depuis l'ouverture sur Internet.
- journal d'audit (migration 019,
inc/audit.php), branché sur les sept sorties detry_login. ⚠ Le refus reste identique pour toutes les causes — les distinguer permettrait d'énumérer les comptes ; la vraie cause va au journal, lisible par qui est déjà entré. ⚠ Le mot de passe essayé n'y figure jamais : la variante presque juste est souvent le vrai mot de passe d'un autre service ; - plafond par adresse, qui complète celui par compte sans le remplacer :
LOGIN_MAX_FAILprotège un compte contre l'acharnement, celui-ci protège tous les comptes contre quelqu'un qui en essaie mille sans jamais insister.
⚠ Ne pas savoir n'autorise pas à barrer : table absente ou base muette, le plafond s'efface. Sinon un journal indisponible verrouillerait l'exploitant hors de son propre panel.
Les décisions sur les salons ne dorment plus
chanq_mail_dispatch() n'avait qu'un déclencheur : le poll ?p=status. Un propriétaire n'apprenait le sort de sa demande que si un opérateur ouvrait le panel — une décision du vendredi soir attendait le lundi. PHP n'a pas de battement de cœur propre ; overtchat-chanq-mail.timer en donne un (5 min).
⚠ Le verrou anti-double-envoi est dans la fonction, pas chez les appelants : les deux peuvent tomber en même temps. ⚠ Et l'unité porte PrivateTmp=false — avec true, systemd donnerait au service son propre /tmp, le verrou ne serait plus partagé, et le double envoi reviendrait. Un durcissement qui défait une garde n'en est pas un.
Éprouvé sans intervention : une décision posée à 22:45 traitée par le timer seul à 22:49, un seul message parti, réception confirmée.
Un banc refuse les appels à des procédures inexistantes
IriX/tests/test-appels.tcl croise les appels ::soc:: avec leurs définitions. Il dérive des sources et n'énumère rien.
⚠ Il a trouvé un vrai défaut à son premier passage : ::soc::oper::set_online, appelée au RAW 313 sous le commentaire « met à jour le statut oper en mémoire ». Cette procédure n'a jamais existé — l'appel levait à chaque WHOIS d'IRCop, avalé par son catch.
Créations
IriX/tests/test-appels.tcl · IriX/tests/test-chanq-robots.tcl · Web/tests/test-clientip.php · Web/tests/test-audit.php · Web/inc/audit.php · Tools/chanq-mail.php · Config/systemd/overop@.service (template, trois instances) · Config/systemd/overtchat-chanq-mail.{service,timer} · Web/sql/018-opmybot-multibot.sql · Web/sql/019-web-audit.sql
Avertissements
OverOp-QetOverOp-Ssont décidés (§14), pas posés. Un seul robot existe ;- l'expiration des pseudos n'a aucun sujet : un compte en base, sans adresse ;
- trois décisions d'exploitation restent ouvertes — mettre la modération en service, trancher le niveau de
!banall/!unbanall/!kickall, statuer surIriX-sql-fixes.tcl; - l'IPv6 n'a toujours pas été sondée depuis un réseau extérieur.
4.13 1 version · 4 août 2026 2 bugs corrigés 3 avertissements
4.13.0 — 4 août 2026
Un droit qui ne valait que sur le réseau vaut désormais par salon — et quatre mécanismes qu'on croyait en service ne tournaient pas. Le fil de la journée n'est pas le lot demandé : c'est le nombre de choses qui paraissaient fonctionner. Une campagne écrite mais jamais lancée, un banc qui tombait un jour sur sept, un autoop qui n'a jamais rien posé, une identification qui ne tombait pas. Aucune ne se signalait ; toutes se sont vues en provoquant l'effet.
Le chat du site peut passer opérateur
/OPER était refusé depuis la page Chat : « Ce compte d'opérateur exige une connexion TLS. » Le navigateur était pourtant déjà en wss://.
Le chiffrement s'arrêtait à Apache, qui relayait en ws:// vers un listener sans tls. Le serveur voyait donc une connexion nue, ne posait jamais +z, et require-tls yes refusait. Un sélecteur « TLS ou non » sur la page n'aurait rien changé : le choix ne se joue pas dans le navigateur mais sur le dernier saut.
- listener
8000enwebsocket; tls;, Apache enwss://— ⚠ les deux vont par paire, changer l'un sans l'autre ferme le chat ; SSLProxyCheckPeerName offn'est pas un relâchement : le serveur présente le certificat du réseau et on l'atteint par127.0.0.1. Aucune autorité n'émet de certificat pour une boucle locale. Ce qui garantit l'interlocuteur, c'est que la destination ne quitte pas la machine ;- l'autre voie — croire un en-tête
X-Forwarded-Proto— aurait posé+zsur une déclaration du relais, jamais sur un fait.
Campagne test-ws-tls.py, 8 constats, deux serveurs. Le témoin (même bloc oper, port sans tls) reproduit le refus : sans lui, un OPER réussi ne dirait pas que le TLS y est pour quelque chose.
Trois campagnes existaient et ne tournaient nulle part
En inscrivant la nouvelle campagne au lanceur, on constate que test-filtre, test-robots et test-forward n'y étaient pas. Elles ne s'exécutaient que si quelqu'un pensait à les lancer à la main — c'est-à-dire jamais, une fois leur lot terminé. test-forward datait de la veille.
⚠ Une campagne hors du lanceur ne protège de rien : elle donne seulement l'impression que le sujet est couvert. Le lanceur passe de 11 à 15.
⚠ Et le vrai enseignement : Tools/bancs.sh découvre par glob, donc aucun banc ne s'était perdu ; run-tests.sh énumère à la main, et trois campagnes s'y étaient perdues. Ce qui est dérivé ne vieillit pas.
Les grades par salon
${t}_users porte le grade sur le réseau ; rien ne disait ce qu'une personne peut faire sur un salon. chanadmin est un drapeau du salon, opmybot une présence, owner_account un propriétaire — aucun ne s'en déduit.
IriX_chan_access(salon, compte, grade, posé par, date), clé étrangère vers le référentielIriX_gradesdéjà en base : un grade inventé devient impossible au lieu d'être improbable ;- ⚠ aucune clause
COLLATE, à l'inverse des autres migrations : cette table se joint aux tables du bot, qui prennent la collation du serveur. Créer la divergence ici aurait levé 1267 à chaque jointure — le défaut exact qui avait arrêté la migration015à mi-chemin la veille ; Web/inc/chanaccess.php— la garde, en fonctions pures. 36 constats, 5 mutations, 5 détectées ;?p=salonadminet la section « Accès réservé » de?p=salon.
⚠ La barrière ne compare aucun mot de passe. L'intention première était de lire IriX_users.password et de comparer le SHA-256 en PHP ; inc/ircauth.php avait déjà tranché l'inverse, pour deux raisons qui valent ici mot pour mot : connaître un mot de passe ne prouve pas qu'on est la personne — le bot, lui, voit qui porte le pseudonyme sur le lien —, et un second vérificateur dans un autre langage finit par diverger sans que rien ne le montre. Conséquence annoncée plutôt que cachée : il faut être connecté sur IRC.
Un « op » n'est plus op partout
do_autoop lisait le grade réseau une fois, avant sa boucle, puis l'appliquait à tous les salons. Il est désormais demandé par salon.
| Qui | Ce qu'il obtient |
|---|---|
ircops, admin, csowner | leur autorité partout — un IRCop doit pouvoir intervenir sur un salon en crise |
csop / csvoice seuls | plus rien, sauf là où un grade de salon existe |
| compte non authentifié | rien, et le contrôle est avant la lecture de la table |
⚠ Sans cette dernière garde, prendre le pseudonyme de quelqu'un — sans son mot de passe — suffirait à recevoir son +o.
halfop reçoit enfin son +h. ⚠ ishalfop n'est pas supposée présente : elle dépend des options de compilation d'Eggdrop, et un appel à une commande absente arrêterait l'autoop pour tous les salons suivants de la boucle. info commands tranche sur l'interpréteur qui tourne vraiment.
Prouvé sur le réseau, les deux sens, en ne changeant qu'une ligne en base : MODE #!Overtchat! +o avec le grade de salon, rien avec csop='oui' seul.
Trois mécanismes qui ne tournaient pas
IDENTIFYd'OverNick n'appelait jamaisdo_autoop. Il posaitauthet+r, puis s'arrêtait ; seule une commande privée que plus personne n'emploie déclenchait l'autoop. ⚠ Et cela a failli tromper l'essai : il attendait « pas de+o», ne l'a pas reçu, et allait conclure au succès — alors que rien ne s'exécutait. Un résultat conforme n'est pas une preuve tant qu'on n'a pas vérifié qu'il vient de la cause qu'on croit.- L'autoop ne pouvait de toute façon rien poser.
irix-operjoin.tcldiffère l'entrée sur les salons jusqu'à l'identification, etdo_autooppart au même instant en ne traitant que les salons où l'on est déjà présent : il arrivait systématiquement trop tôt. ⚠ Deux réglages pour une même idée —autoopà l'identification,automode_userau JOIN —, et seul le premier était armé. - L'identification ne tombait pas à la déconnexion.
evt_deauth_nickinterroge le registre du lien, tenu par le dispatcher S2S sur un chemin distinct du bind Eggdrop, sans ordre garanti : le registre montrait encore le pseudonyme etauth='oui'survivait. Révocation différée de 4 s.
⚠ La première correction du deauth était fausse, et c'est le banc qui l'a dit. Forcer la révocation au QUIT cassait un cas que test-auth.tcl garde : deux sessions du même pseudonyme, la première qui part, la seconde déjà identifiée. Différer laisse le registre s'actualiser et reste juste des deux côtés.
⚠ Cas résiduel écrit dans le code plutôt que masqué : une reconnexion en moins de 4 s sans se ré-identifier conserve l'ancienne auth. Le fermer demande de raisonner sur l'UID de session, pas sur le pseudonyme.
⚠ Le balayage horaire exempte les IRCop — il ne rattrape donc jamais le compte le plus puissant du réseau. Signalé, non modifié : c'est une décision.
Un opérateur n'avait droit qu'à un salon
join_chan et oper_chan ne portaient qu'un nom. #Csadmin étant en +i, il fallait choisir entre lui et le salon des services. Ils acceptent désormais une liste séparée par des virgules (operjoin::salons_de, pure — 14 constats).
⚠ Une mutation a survécu et a montré qu'une garde était redondante avec une autre : retirée. Deux gardes qui se recouvrent ne protègent pas deux fois, elles font croire que la seconde sert.
Bugs corrigés
test-std.tcltombait tous les mardis : sa regex cherchaitMarsans limite de mot, et « Mardi » le contient. Le code avait raison. Un banc qui tombe selon le jour de la semaine est pire qu'un banc absent — on apprend à ignorer son échec.bancs.shannonçait « neuf campagnes » alors qu'il y en a quinze : le nombre est désormais dérivé du lanceur.
Demander un robot : trois acteurs, aucun ne fait le travail d'un autre
Tranché par SerVuS : le propriétaire demande, l'IRCop accorde, le bot exécute. Le propriétaire dépose un salon et un motif — il ne choisit ni le robot, ni les pouvoirs, ni le terme : un mandat engage le réseau, et c'est le réseau qui en fixe l'étendue.
⚠ L'approbation N'ACCORDE PAS le mandat. Elle inscrit une intention ; le bot la réalise au cycle suivant et écrit ce qui s'est vraiment passé. Le site n'a pas de lien serveur : il ne peut constater ni l'entrée du robot, ni ses pouvoirs. Sans cette séparation, le panel annoncerait « robot accordé » sur la foi d'un UPDATE. approved et done sont donc deux états distincts.
⚠ La propriété est re-vérifiée à l'approbation — un salon peut changer de main entre le dépôt et la décision. ⚠ L'ordre des refus ne renseigne personne : le refus de droit passe avant « demande déjà en attente », sinon un inconnu apprend quels salons existent. ⚠ Un refus sans motif est impossible.
Prouvé de bout en bout : SOC MANDAT: OverOp-H prêté à #accueil … par panel (demande #1), mandat actif, demande done. Essai nettoyé.
Une session d'opérateur ne reste plus ouverte
⚠ Le balayage exemptait les IRCop — « un opérateur est toujours présent ». Vrai quand IriX ne voyait que ses propres salons ; périmé depuis que le lien lui montre tout le réseau. Conséquence : le seul filet de secours ne rattrapait jamais le compte le plus puissant du réseau. Exemption retirée à la demande de SerVuS ; un opérateur réellement connecté n'est pas touché.
Avertissements
- ⚠ Le réglage
automode_useret la listeoper_chanvivent en base, hors dépôt : une base recréée les perd. - ⚠ Poser un grade sur la page ne change rien sur le réseau tant que le salon n'a pas
automode_userarmé — et la page le DIT désormais, dans les deux sens. - ⚠ Il reste un cas où
authsurvit à une déconnexion : une reconnexion en moins de quatre secondes sans se ré-identifier. Le fermer demande de raisonner sur l'UID de session, pas sur le pseudonyme.
4.12 1 version · 3 août 2026 non ventilé
4.12.0 — 3 août 2026
Le prêt de robot va jusqu'au bout, et trois défauts ne se sont montrés qu'en production. Les bancs prouvent une décision ; ils ne prouvent ni qu'un ordre traverse le réseau, ni qu'une migration s'applique, ni qu'une page s'affiche. Chacun des trois lots de cette version a été démenti au moins une fois par le réel après avoir été vert au banc — et c'est la partie utile de la journée.
Le retrait d'un robot prêté (§9bis du cahier)
Le protocole ne connaissait qu'un sens : le robot demande, OverBot répond. La fin d'un prêt demande l'inverse.
ordre BOTCMD <version> <corr> PART <#salon> [motif]
accusé BOTACK <version> <corr> <issue> [détail]
- OverBot n'attend pas l'accusé et n'en garde aucun état : un mandat est un DROIT, pas une présence. La fin est écrite avant que l'ordre parte ; l'accusé informe, il ne conditionne rien.
- On ne répond jamais à un accusé — deux machines qui s'accusent réception l'une l'autre n'ont aucune raison de s'arrêter.
MANDATFINannonce « ordre de retrait envoyé », jamais « le robot est sorti ». Cette prudence a servi le soir même : au premier essai réel, le robot a échoué sur son dernier geste, et l'annonce était déjà exacte.- ⚠ Le sens décide de la primitive : le dispatcher ne route que les PRIVMSG. Un accusé en NOTICE aurait été bien formé et lu par personne.
::soc::mandat::balayerpasse à l'heure :echusavait vécu un jour entier sans appelant, et un prêt de quatre-vingt-dix jours durait indéfiniment.
L'allègement des modules au déploiement
Quarante et un pour cent des lignes des vingt modules sont des commentaires. Ils portent la mémoire des incidents, pas la description du code : on dépouille donc au déploiement, jamais à la source. 24 069 → 14 249 lignes sur la machine, le dépôt garde tout.
⚠ Un # dans un corps de switch est un MOTIF, et une accolade isolée dans un commentaire compte pour l'équilibrage. D'où la règle : on ne retire que ce dont on est certain — un bloc n'est allégé que si la commande qui l'ouvre prend un script. Le pire cas est qu'on allège moins ; jamais qu'on casse.
La page privée d'un salon (lot 3)
La chaîne à trois maillons est fermée : compte web → pseudonyme prouvé → channels.owner_account. ⚠ added_by n'était pas le propriétaire : c'est l'opérateur qui approuve. Les confondre aurait donné la propriété de chaque salon à qui l'a ouvert.
⚠ Un salon sans propriétaire n'appartient à personne — surtout pas à tous. Deux chaînes vides sont égales, et cette égalité aurait ouvert tout salon orphelin à qui n'a pas d'identité.
Les faux clones du webchat
Le correctif n'est pas WEBIRC : il n'y a aucune passerelle, le navigateur parle directement au serveur. WEBIRC suppose un mot de passe partagé, qui serait public dans du JavaScript. Le serveur adopte donc X-Forwarded-For, mais seulement d'un relais déclaré (set::websocket-proxies, vide par défaut).
Ce que le réel a démenti, et qu'aucun banc ne voyait
| Ce qui était vert | Ce que la production a montré |
|---|---|
| le retrait, 91 constats | channel set … inactive — il manque le +, les options d'Eggdrop sont des drapeaux |
la migration 015 | deux tables du projet, deux collations : erreur 1267, migration arrêtée à mi-chemin |
| la page de salon | tbl() rend un identifiant avec ses accents graves — la garde cherchait une table nommée `IriX_channels` |
Trois leçons de banc en sont sorties : un bouchon plus permissif que la commande qu'il imite ne prouve rien ; un script de mutation doit distinguer trois issues et non deux (vert, rouge, planté) ; et un lsearch bredouille rend -1, qui précède tout — un geste absent passait pour bien ordonné.
Épreuves
30 bancs, 2 223 constats (+ la campagne serveur test-forward.py). Le retrait a été joué sur le réseau avec un OverOp posé : quatre traces concordantes et la même corrélation des deux côtés.
4.11 3 versions · 1er août 2026 → 2 août 2026 13 créations 10 évolutions 18 bugs corrigés 6 constats 10 erreurs 5 avertissements
4.11.2 — 2 août 2026
Huit lots, une seule cause. Un fait écrit à deux endroits, l'un mis à jour, l'autre pas — et rien qui le signale. Une route de menu, un tableau de compatibilité, un catalogue d'installation, cent vingt-quatre grades, une phrase cherchée dans un journal : à chaque fois deux listes du même fait, dont une seule avait suivi. Rien ne cassait, c'est ce qui rend ce défaut long à voir.
Créations
Web/inc/routes.php— la table des pages du panel, sortie d'admin.phpoù aucun banc ne pouvait la lire. Déplacer la liste n'est pas cosmétique : c'est ce qui rend le contrôle possible.Web/inc/commandes-anope.php— les 249 commandes d'Anope, extraites et non écrites : le nom tapé vient descommand { service; name }de sa configuration, la description desSetDesc(_("…"))de ses modules, la traduction de son fichier français. Extracteurs dansTools/anope-commandes.pyetTools/anope-commandes-php.py.- Quatre bancs :
test-routes.php(22),test-grades.php(18),test-commandes.php(14), et 60 vérifications neuves danstest-compat.php. Web/tests/test-vues.php(17) — la question d'après celle detest-routes.php: une fois la page atteinte, s'affiche-t-elle ? Il vérifie que chaque page routée appelle le gabarit, et que chaque classe qu'elle écrit en dur existe dans une feuille de style. Les appels sont cherchés par les jetons de PHP, jamais parstrpos: le nom d'une fonction apparaît aussi dans les commentaires, et une recherche de texte y verrait un appel qui n'existe pas — le genre de repère qui passe au vert sur un défaut intact.Web/tests/test-motd.php(34) — le message d'accueil dit-il ce que le serveur fait ? Les deux fonctions pures du générateur, extraites du fichier et jamais incluses — l'inclure écrirait le MOTD de production —, le modèle réel, et l'unité systemd versionnée. Trois repères tiennent le fond plutôt que la forme : la branche filtrée annonce le filtre, la branche nue ne l'annonce PAS, etAPPELsurvit dans les deux.IriX/tests/test-doublons.tcl(30) — deux règles de chargement qu'aucune erreur ne signale : aucune procédure définie deux fois parmi les dix-huit modules, et aucune procédure sur un namespace pas encore déclaré. L'ordre de chargement est dérivé dessourcedu point d'entrée. Il compare aussi les deux écritures du schéma deweb_authqetmodq— colonnes et valeurs d'ENUM.Web/tests/test-ulines.php(34) — ce qu'un lien ULine accepte selon son état.socket_refusal()n'était éprouvée par rien, alors que c'est elle qui grise les boutons ET qui fonde le refus côté serveur.
Bugs corrigés
- La page « Protection » du panel renvoyait au tableau de bord. Trois listes décident qu'une page existe — l'entrée de menu, la route, le fichier — et elle en avait deux sur trois. Rien n'échouait : le lien s'affichait, une page s'ouvrait, et ce n'était pas la bonne. Le repli se nomme désormais, à l'écran et au journal ; un repli muet est ce qui a rendu ce défaut invisible une journée.
- La même page, une fois atteinte, s'affichait toute nue.
admin.phpne fait qu'uninclude: il ne pose NI l'en-tête NI le pied. Chaque page produit son propre<!doctype>, son<link>versapp.csset son ossature en appelantrender_header()etrender_footer()— « Protection » n'appelait ni l'un ni l'autre. Fond blanc, cartes disparues, tout en texte brut, et rien au journal. C'est le troisième maillon de la même chaîne, et il se casse aussi silencieusement que les deux premiers. - Deux autres pages n'appelaient pas
render_footer()—connexionsetopers. Moins voyant, pas anodin : le script qui rafraîchit le bandeau d'état vit dans le pied, donc leurs pastilles restaient figées sur la valeur du chargement. Un panel de supervision qui montre un lien tombé comme encore actif est pire qu'un panel muet. - Onze classes CSS n'existaient dans aucune feuille. Sept sur « Protection » —
page-head,lead,switch-row,grid-2,btn-primary,btn-warn,table—, plus unbadge badlà où le vocabulaire ditbadge err; et trois ailleurs :table-wrappourtbl-wrap,row,tbl. Une classe inconnue ne produit aucune erreur : l'élément se rend, sans le style attendu. - La page Compatibilité annonçait quatre serveurs et en montrait six. Le premier tableau bouclait sur la table, les trois grilles du bas récitaient un
$DIALECTESfigé à quatre. Colonnes, largeurs, titre, sommaire et description de référencement sont maintenant dérivés. Les largeurs écrites à 17 % la pièce faisaient 102 % à six colonnes, et la grille sortait de son cadre. - L'installateur ne proposait pas Bahamut, dont le site annonçait pourtant qu'IriX le parle. Vingt points d'accroche, dont les DEUX catalogues — celui qui crée
servers.jsonet celui qui complète un fichier existant, sans lequel une installation en service ne l'apprendrait jamais. - La page Commandes annonçait fermé ce que le code laisse ouvert. Dix écarts :
hello,passdu premier lancement,auth,help,!help,!infoet!statusétaient données pour « user » alors qu'elles ne testent RIEN ;syncownerexige le flagnd'Eggdrop, pas un grade de salon ; et deux commandes manquaient —approve, par laquelle un IRCop valide une réinitialisation de mot de passe du site, etVERIFY, que le site DEMANDE de taper pour finir une inscription. - La table des protocoles d'Anope ignorait
bahamutdepuis sa naissance : un lien bahamut y retombait sur « inconnu ». - Le MOTD promettait un filtre que le rechargement venait de couper.
conf_load()rappellemsgfilter_load()à chaque REHASH, mais le générateur n'était accroché qu'à l'ExecStartPost— le démarrage. Viderfiltre-messages.confpuis recharger laissait le message d'accueil affirmer à chaque connexion que les messages privés sont protégés. Ce n'est pas une ligne périmée : c'est la promesse sur laquelle quelqu'un décide de ce qu'il écrit. Second point d'accroche à l'ExecReload, avec un mode qui attend la ligne de journal du rechargement en cours — le HUP est asynchrone — et sans repli sur le fichier de motifs, qui après un rechargement raté dirait ce que le serveur aurait dû charger. - Réparer un lien ULine coupait les autres. L'alerte « des liens sont rompus » nommait les liens en panne, puis offrait un bouton unique qui passait en
reloadTOUS les sockets activés — orreloaddéconnecte et reconnecte deux secondes plus tard. C'est l'incident du 31/07 qui avait coupé la production (point 16 dePROJET.md). L'alerte porte maintenant un bouton par lien rompu, et le drapeau global n'agit plus que sur les liens qui ne sont pas connectés. Il programmeconnectet nonreload: le panel REFUSAIT déjà unreloadsur un lien non établi pendant que le drapeau en posait un — deux logiques contradictoires pour le même geste. - Quatre procédures d'IriX étaient définies deux fois, le point ouvert n'en annonçait que deux.
IriX-sql.tclsourceIriX-table.tclà sa ligne 30 puis redéfinit plus bas :svc::db_init,auto::db_initetauth::db_initne s'exécutaient jamais, pas plus que la copie de::soc::sql_escdansIriX-sql-IA.tcl. Rien ne cassait — on corrigeait le schéma dans le fichier mort, on redémarrait, et rien ne changeait. Lasvc::db_initmorte décrivait d'ailleurs un schéma périmé (sansroleniaction_pending) et créaitmodq/web_authq, dont le schéma qui fait autorité est celui des migrationsWeb/sql/. Retiré, avec la même pierre tombale que pouria::db_initle 19/07.
Évolutions
- Anope devient un groupe du menu, avec trois états par pseudo-client : Online (vu dans
${t}_online, c'est le réseau qui répond), Offline (absent et cohérent), Warline (il devrait être là et n'y est pas). Sans le troisième, un service tombé s'écrirait comme un service volontairement coupé, et l'on ne regarderait pas. - La page Commandes se choisit son service — un menu à deux niveaux, IriX et Anope. On ne consulte jamais « toutes les commandes » : on cherche ce que sait faire OverNick, ou ce que ChanServ attend.
- La pastille du menu ne relance plus un
psà chaque page.pid_of()liste tous les processus de la machine et les filtre en PHP : 12 ms, payés à chaque clic pour une pastille qui ne change qu'aux démarrages. Relais de 20 secondes en session, effacé paranope_ctl()— sans quoi démarrer Anope laisserait le menu montrer le contraire, et l'on referait l'action en croyant qu'elle a échoué. Mesuré : 30 ms la première fois, 0,04 ms ensuite. test-grades.phplit les sources d'IriX et en dérive la garde exigée, commande par commande, plutôt que de comparer à des grades recopiés dans son propre code. Les commandes qui gardent leur porte autrement — session lue à la main, flag Eggdrop, WHOIS — sont déclarées en exceptions, et le banc vérifie que chacune est encore fondée.
Erreurs
- Un banc vert ne prouve rien s'il compare à une liste recopiée. Le repère « les cinq dialectes sont couverts » de
test-anopea été mis à jour EN MÊME TEMPS que la table qu'il contrôlait : il est resté vert pendant que la table mentait. Il lit désormais les dialectes dansirix-proto.tcl, à leur source. test-irix.pyrendait 26 sur 27 depuis le 30/07, et ce n'était pas un défaut. Le contrôle cherchait la phrase « EOS reçu » ; ce jour-là, la détection de fin de burst avait été refaite pour reconnaître leEBd'ircu et le message était devenu « fin de burst reçue (EOS) ». Le burst se closait parfaitement. Un banc qui atteste un fait en cherchant une PHRASE mesure la stabilité du vocabulaire, pas celle du code : il lit maintenant l'étatsock_statepublié par le banc Tcl. 27 sur 27.- Une mesure prise au mauvais endroit est une mesure fausse. Le coût du menu Anope a d'abord été annoncé à 22 ms, mesuré depuis
/mnt/coù le moindreis_filecoûte cent fois son prix. Sur le disque de production, la valeur était tout autre. - Un
\bdans une chaîne Python non brute donne un BACKSPACE, pas le motif d'expression régulière. Une heure perdue sur un extracteur qui ne trouvait plus rien : le fichier contenait l'octet 0x08. - Le tableau des grades se disait rangé « du plus faible au plus fort » et listait
useravantpublic: les deux portaient le même numéro. Invisible tant que l'un ne servait qu'aux services et l'autre qu'au robot. - La table des bancs de
ROADMAP.mden listait treize, datés du 31/07, alors qu'il y en a vingt-quatre. Reprise, séparée entre ceux qui se lancent seuls et ceux qui exigent un serveur en face, et elle dit maintenant comment se recompter sans elle.
Avertissements
!infoet!statussont ouverts à toute personne présente sur le salon, compteurs de comptes et de salons compris. C'est l'état du code, désormais documenté comme tel — pas une décision prise ici.- Le certificat de Bahamut est généré à l'installation, en
-batchet seulement s'il manque. Sansircd.crt, ce serveur n'ouvre aucun port, pas même en clair, et rien dans son message ne désigne la cause.
Vérifié
- Neuf bancs PHP : compat 260, statut 85, anope 78, update 50, protection 34, chatmod 23, routes 22, grades 18, commandes 14. Quatre bancs Tcl autonomes : proto 501, needop 32, sessions 30, nickexpire 26.
test-irix.py: 27 sur 27, éprouvé par deux mutations — la variable d'état renommée, puis le serveur privé de sonEOSet recompilé (7 sur 22, le lien ne monte plus).test-grades.phpéprouvé par neuf mutations, toutes mordues. Deux premières tentatives n'ont rien prouvé : la mutation ne s'était pas appliquée.- Page Commandes rendue hors navigateur dans trois vues et deux tris, et servie en production.
4.11.1 — 1er août 2026
Le robot du réseau réclamait l'op à personne. Un correctif de la 4.11.0 lui avait fait perdre le sien ; celui-ci le lui rend, et fait taire la demande qui tournait à vide.
Bugs corrigés
- 120 fois la même ligne, une par minute. Depuis 15h08, le journal portait « Besoin d'op sur #!Services! - Mode réclamé : +o » sans que rien ne réponde. Ce message était écrit sur le salon ; or les messages que le dispatcher route portent un service en cible, celui-là portait un salon. Il partait dans le vide.
- Deux défauts, pas un. Le message était inutile — et il aurait été dangereux s'il avait été utile : accorder l'op sur la foi d'une phrase lue sur un salon, c'est l'accorder à quiconque sait la recopier. La demande naît désormais dans le code du bot, pour son propre pseudonyme, et rien de ce qui vient du dehors ne peut la déclencher.
- Pourquoi il avait perdu l'op : depuis la 4.11.0, les cinq pseudo-clients créent le salon de service au moment du lien, et le robot, qui le rejoint ensuite, entre sur un salon qui existe déjà — un client ordinaire n'y est pas opérateur. Rendre l'op aux services lui avait pris le sien.
- Les trois autres besoins étaient logés à la même enseigne : invitation, clé, limite écrivaient eux aussi sur le salon. L'invitation passe désormais par un service ; la clé n'est plus imposée au salon — la poser d'autorité reviendrait à changer la clé des autres pour se laisser entrer — et la limite ne nous appartient pas : on le dit, et on n'entre pas.
Évolutions
- Le mécanisme se tait quand il échoue : trois essais, puis une demi-heure de silence — annoncée, datée, et la reprise l'est aussi. Un mécanisme qui renonce sans le dire est indiscernable d'un mécanisme en panne.
- Le lien employé est choisi sur la présence du robot, non sur « le premier debout » : deux liens peuvent mener à deux réseaux, et un mode posé du mauvais côté viserait un salon qui n'existe pas là-bas.
Erreurs
botisircopsne dit pas ce que son nom promet. Elle ne teste pas si le robot est opérateur IRC : elle lit le grade de son pseudonyme dans la table des utilisateurs. Le robot n'y étant pas inscrit, elle répondait « non » quel que soit son état réel — c'est ce qui envoyait la branche de repli à chaque appel. Signalée depuis longtemps au journal des défauts (H-3), sans que la cause ait été nommée. Elle subsiste ailleurs, avec le même malentendu.
Vérifié
- Mesuré avant et après, en production : 134 lignes « Besoin d'op » dans la journée, 0 depuis le rechargement. Et dans l'autre sens — l'effet voulu est là :
MODE #!Services! +o IriXémis par OverTchat, puis « op obtenu après 1 tentative(s) », le serveur ayant renvoyé le mode. IriX/tests/test-needop.tcl, neuf : 32 vérifications hors lien sur le code réel. Sept mutations les font tomber.
4.11.0 — 1er août 2026
Les services cessent d'être expulsables, et le sixième dialecte parle. Deux chantiers sans rapport apparent, réunis par la même leçon : ce qui protège doit être vérifiable de l'extérieur, et ce qu'on croit générique porte presque toujours l'hypothèse d'un serveur précis.
Créations
- Protection des services et des robots —
src/protection.cdans Overtchat-Hybrid. Les trois gardes du serveur — expulsion (m_channel.c), déconnexion (m_oper.c), bannissement réseau (ban.c) — posent désormais une seule question,protection_shields(). Un robot est protégé s'il porte le mode+Bet figure dans la liste écrite par le panel : le mode se pose soi-même, il ne saurait valoir immunité. conf/protection.conf, déclaré par une directiveprotection = …et tenu à part de la configuration principale : le panel doit pouvoir l'écrire sans jamais toucher au fichier qui porte les mots de passe. Deux interrupteurs indépendants,servicesetrobots. Fichier absent ou valeur incomprise ⇒ protection ACTIVE — un réglage illisible ne doit pas désarmer une garde.tests/test-robots.py— douze constats sur la protection.- Le dialecte
bahamut(irix-proto.tcl), écrit d'après les sources du serveur : il n'a aucune documentation de protocole. IriX/tests/lien-bahamut-bot.tcletIriX/tests/essai-bahamut.py— le vrai code du bot contre un vrai Bahamut, jugé par un client témoin.- Le suivi d'un identifiant qui change — primitive de dialecte
id_apres_renommage, et::soc::ulines::auto::reclef_utilisateur, qui reclasse en un seul endroit tout ce qu'IriX tient d'une personne.
Évolutions
- Six dialectes décodables :
bahamutrejointunreal,och,hybrid,p10etinspircd. Chacun n'y est entré qu'après un lien monté par le bot lui-même contre le serveur réel — jamais sur la foi d'un banc hors lien. - Le panel règle la protection et dit lorsque le serveur ne l'applique pas encore, plutôt que d'afficher un réglage sans effet.
- Le bot se déclare robot :
::soc::modes_du_botlui pose+iB. Il tenait le salon de service sans aucune protection — un opérateur reste expulsable par un autre. - Le burst de salon d'UnrealIRCd est enfin lu (
SJOIN), et l'injection reprend l'horodatage du salon annoncé par le serveur.
Bugs corrigés
- Les cinq pseudo-clients entraient sur
#!Services!sans l'op, depuis toujours. Eggdrop se connecte d'abord comme client et crée le salon ; le lien serveur monte dix secondes plus tard, et sesSJOINportent un horodatage plus récent — la règle du TS écarte alors les préfixes demandés. Invisible jusque-là parce qu'un remplacement du serveur vide le salon. Ce qui l'avait caché :SJOINn'était traduit en aucun verbe connu du dispatcher, si bien que le burst de salon n'était jamais lu en production. - Un renommage périmait les registres en silence sur un dialecte où l'identifiant est le pseudonyme. Ils ne répondaient pas « faux » mais « inconnu », pendant que les anciennes clés s'accumulaient.
- Le verbe
BURSTen désignait trois sous Bahamut : l'ouverture du burst du serveur, sa fermeture — c'est l'argument qui les sépare — et, par la traduction deSJOIN, le burst d'un salon. Le lien montait puis mourait au délai.
Erreurs
- Un talon de banc rendait un lien sourd. Le talon générique rend une chaîne vide, le vrai code écrit
if {[::soc::svc::cmd_common …]}, et Tcl refuse. L'erreur survient dans un gestionnairefileevent— que Tcl supprime dans ce cas : le lien restait ouvert et ne lisait plus rien après le premier message d'un témoin, pendant que le constat « aucune erreur Tcl » répondait 0. On en a conclu pendant une heure que le serveur ne propageait pas les changements de pseudo ; un sniffer branché sur le même lien a montré qu'il les propageait très bien. SVSNICKsur son propre pseudo-client ne prouve rien : le serveur route la commande vers celui qui possède le client, c'est-à-dire nous. La ligne revient telle qu'elle est partie.- Bahamut refuse en silence les connexions trop rapprochées — trois depuis une même adresse en quinze secondes, et le refus se fait à l'
accept, sans rien journaliser. Un témoin jamais entré se lit alors comme un défaut de propagation.
Mise en service
- Overtchat-Hybrid recompilé et en service depuis le 01/08/2026 à 15h06, dans cet ordre : binaire d'abord, directive ensuite. Ancien binaire sauvegardé, configuration validée par le nouveau binaire avant remplacement,
protection.confposé en 0640, puisreload. - Vérifié par un netadmin qui essuie le refus :
484 :Cannot kick protected user OverTchatet485 :Cannot kill protected user OverNick. Une protection qu'on ne peut pas voir échouer n'est pas vérifiée.
Avertissements
- On ne rétablit pas l'op des services depuis l'extérieur. Un
MODE +ooooolancé depuis un compte netadmin est appliqué, puis IriX expulse son auteur (« Abus d'une désynchronisation ») et l'op ne tient pas. Seul un redémarrage d'IriX corrige. - Six robots sont déclarés dans la liste du panel ; un seul en avait besoin, le bot lui-même. Les cinq pseudo-clients sont couverts par l'interrupteur
services, pas par celui des robots. - Le défaut EOS de
tests/test-irix.pyreste ouvert : 26 constats sur 27, et il préexistait à ces changements.
4.10 1 version · 31 juillet 2026 3 créations 4 évolutions 6 bugs corrigés 1 suppression 2 erreurs 2 avertissements
4.10.0 — 31 juillet 2026
Le projet cesse de supposer qu'il est le seul, et qu'il s'appellera toujours pareil. Neuf lots depuis la 4.9.3, qui ont tous la même racine : des noms et des chemins écrits en dur du temps où il n'existait qu'une installation, la mienne. Le numéro mineur et non correctif le dit — rien n'est cassé, mais l'installateur, le serveur et une dizaine de fichiers ne se comportent plus de la même façon chez quelqu'un d'autre.
Créations
- Avis de travaux —
Web/inc/travaux.phpetTools/travaux.php: un bandeau sur les pages publiques et une pastille au panel pendant qu'on modifie le site. Distinct de la page de maintenance Apache, qui répond à la place du site ; ici le site est servi, on prévient seulement qu'il bouge. L'état vit dansConfig/travaux.json, un fichier et non une table — la base fait partie de ce qui peut être en travaux. Deux actions au panel, niveau admin. - Contrôle du manifeste au
pre-commit— le hook compare lessourcedeIriX-sql.tclaux deux listes de l'installateur et refuse le commit en cas d'écart. - Purge de l'état de mise à jour —
_upd_state_purge_exclusretire deupdate_state.jsonce que le balayage refuserait aujourd'hui.
Évolutions
- Le nom de l'installation se choisit au premier lancement —
--projet=,OVERTCHAT_PROJET, ou la question de l'assistant. Résolu avant$BASE_DIRet les vingt chemins figés enreadonlyqui en dérivent, d'où la relance du script plutôt qu'une correction de variables déjà écrites. « Service-Overtchat » et « Overtchat » sont refusés, pour le répertoire comme pour le nom public du réseau : ils nomment le réseau et le serveur d'origine. Une installation déjà en place ne voit rien changer. - Le domaine du site est confronté au réel avant l'installation : DNS puis HTTP, avec confirmation explicite si quelque chose répond déjà. Un constat, pas un verdict — l'installateur reste utilisable hors ligne.
- Plus aucun fichier ne suppose le nom de l'installation. Trois scripts déduisent leur racine de leur propre place (
readlink -fd'abord), les trois unités systemd la reçoivent à l'installation —install-service.shsubstitue chemins et compte de service —, le hook Let's Encrypt passe par l'environnement. - Overtchat-Hybrid passe en 2.0.0, et son jeton de protocole devient
OCH-. Il annonçaitOVH-1.0.0depuis le commit qui l'a créé, alors qu'il a reçu le WebSocket, le filtre de messages privés, la limitation de débit, les salons interdits et le lien TLS. Majeur parce que le WebSocket et le filtre changent ce que ce serveur est ;OCH-parce que « OVH » est la marque d'un hébergeur et que ce jeton part à chaque connexion, dans le 002 et le 004 — c'est aussi le nom que ce dialecte porte déjà dansirix-proto.tcl.
Suppressions
- Le chemin ULine hérité, en entier — 790 lignes d'
IriX-sql.tcl:detect_conf,sync_init, les sixsync::*,load_config,load_config_sql,save_config_sql,do_ulines_scan,db_ulines_scan_check, le drapeauulines_scan, et les commandes!ulineconf,!ulinesync,!ulineset. Elles visaientunrealircd.conf, que la production n'a plus depuis la bascule du 28/07 surovertchat.conf: elles échouaient à chaque appel, et le scan de démarrage balayait$HOMEpour rien. Restentlog_cmd,require_ircopet!ulinestatus, appelés par le chemin vivant.
Bugs corrigés
_write_default_configbâtissait ses chemins sur$HOMEet non$TARGET_HOME: soussudo, l'arborescence décrite partait dans/root.- Une fonction qui affiche un écran ne peut pas rendre sa valeur par
$( )— l'assistant se relançait avec un--projet=contenant son propre affichage, et la question revenait indéfiniment. Toute boucle de question compte désormais ses tours. irix-nickexpire.tcletirix-chatalerte.tclmanquaient àgit.irix.files: sourcés sans garde, une installation neuve serait morte au chargement. Quatrième divergence du manifeste en dix jours — d'où le contrôle au hook.Config/travaux.json, créé le matin même, n'avait pas été ajouté aux exclusions de la mise à jour : 404 permanent, vingt lignes sous le commentaire qui décrit ce piège..gitattributesne couvrait que douze extensions sur dix-huit :git statusannonçait des modifications fantômes surchat.jsetsite.css. Index renormalisé,.batdu côté CRLF avec les.ps1.API.mddocumentait?p=chat-compte, une route qui n'existe pas — elle s'appelle?p=reservation, en POST seulement.
Erreurs
Config/irix.jsonannonçait la version du projet sous une clé nomméeirix.version, dans un fichier nomméirix.json. Elle a porté 4.6.6, puis 4.9.3 quand elle a été « alignée » — ce qui n'a fait que rendre l'erreur plus crédible. Elle porte celle du module, 6.3.ARCHITECTURE.mdrecopiait un numéro de version en tête — 4.6.0 quandVERSIONdisait 4.9.3 — dans un document qui reproche ailleurs exactement cela aux fichiers de configuration.
Mise en service
- Overtchat-Hybrid 2.0.0 sert le réseau depuis le 31/07/2026 à 18h18. Compilé sans un avertissement, la configuration de production validée par le nouveau binaire avant tout remplacement, l'ancien sauvegardé sous
overtchatd.avant-och-20260731_181829. Le serveur annonce désormaisOCH-2.0.0dans son 002 et son 004. - Le lien d'IriX s'est rétabli seul, les cinq pseudo-clients ont été réinjectés, et un
WHOIS OverTchatlancé depuis un client ordinaire rend319 :@#!Services!— la preuve qui traverse les protections, plutôt qu'un journal muet.
Avertissements
- Les trois unités systemd déjà installées dans
/etc/systemd/system/gardent les chemins qu'elles avaient : seuls les gabarits du dépôt sont corrigés. 186 poignées de main TLS avortées dans la journée du 31/07✅ Élucidé et corrigé le même soir. Ce n'était ni un scanner ni une attaque : c'est le panel qui sonde son propre serveur.port_open()ouvre une connexion TCP, constate que quelque chose écoute, referme — et sur le port TLS, le serveur avait commencé sa poignée de main. La fonction est appelée pour les quatre ports à chaque battement de?p=status, plusieurs fois par minute dès qu'un onglet du panel est ouvert. La répartition horaire le disait : 40 à 9 h et 38 à 13 h, 2 à 15 h quand personne ne regardait.UNEXPECTED_EOF_WHILE_READINGetSHUTDOWN_WHILE_IN_INITdescendent en DEBUG — un pair qui raccroche avant d'avoir rien dit n'est pas un événement. Mesuré avant/après sur cinq sondes : 5 lignes, puis 0. Une vraie anomalie reste visible : unGET /sur le port TLS journalise toujours « http request » en INFO.
4.9 4 versions · 31 juillet 2026 15 créations 14 évolutions 12 bugs corrigés 8 constats 2 erreurs 10 avertissements
4.9.3 — 31 juillet 2026
Appeler à l'aide ne dépend plus du client qu'on utilise, et le message d'accueil cesse d'être un texte figé. La 4.9.2 avait donné un bouton aux visiteurs du site ; restaient ceux qui se connectent avec un vrai client IRC, c'est-à-dire ceux dont le réseau ne sait rien.
Créations
APPEL— commande de service, ouverte à tous. Le pendant, pour les clients IRC, du bouton du chat. Sur les 82 commandes du réseau, aucune ne permettait de demander un opérateur : le filtre de contenu du serveur protège des messages qu'on reçoit, mais protéger n'est pas écouter. Elle est offerte par les CINQ services — quelqu'un qui a besoin d'aide ne doit pas avoir à deviner lequel l'écoute — et écrit dans la même table que le site, donc le module d'annonce et le panel la traitent sans rien savoir de sa provenance.Tools/motd-generer.phpet son modèleircd.motd.tpl— le message d'accueil composé depuis l'état RÉEL du filtre, lu dans le journal du serveur (« N motif(s) de filtre chargé(s) ») et non dans le fichier de motifs, qui peut avoir changé sans rechargement.IriX/tests/essai-appel.py— éprouve la commande depuis un vrai client connecté au port utilisateur.- Un bouton de réconciliation sur la page Statut :
installedremis d'accord avec le disque, sauvegarde horodatée, trace nominative.
Évolutions
- Le message d'accueil ne contenait qu'une ligne — « Bienvenue sur le réseau Overtchat. », 36 octets. Le MOTD riche du dépôt est celui d'UnrealIRCd : il ne sert plus personne depuis la bascule du 28/07. Le nouveau tient en un écran et met l'appel en premier, avant la liste des services : personne ne fait défiler un MOTD pour chercher du secours.
- Trois textes disaient « signalez un abus » sans dire comment — la charte, la page Contact, le MOTD. C'était honnête tant qu'aucun moyen n'existait ; les taire ensuite revenait à donner une consigne inapplicable.
ExecStartPostsur l'unité du serveur, préfixé de-: un message d'accueil périmé est un désagrément, un réseau qui refuse de démarrer est une panne.- La page des commandes liste
APPEL, en public et en commun.
Bugs corrigés
- Un commentaire entre deux branches d'un
switchTcl rendaitAPPELmuette. Le corps d'unswitchest une LISTE de paires motif/script : un#y devient un motif, et la ligne suivante un script. Le journal disait « invalid command name "—" » — le tiret cadratin lu comme une commande — et le client n'obtenait aucune réponse. Le fichier passait pourtant la vérification de syntaxe : il était complet, juste faux. runningétait comparé alors qu'aucun fichier ne peut le tenir à jour. Le message s'affichait sous presque chaque composant, en permanence, sans rien apprendre — au point d'être signalé comme une anomalie du panel. Une information qu'on voit toujours devient le bruit qui masque les autres.Runtime/version.jsonétait suivi par git alors que le panel le réécrit à chaque vérification : le clone de production devenait modifié dès le premier contrôle, et le déploiement suivant a effectivement échoué — merge abandonné, puis « Everything up-to-date » au push. Ignoré,.exampleversionné.- Le bouton d'appel était hors écran : poussé en pied de colonne, il se trouvait à y=1050 sur une fenêtre de 900. Il fallait faire défiler pour demander de l'aide.
Erreurs
- Deux bancs ont d'abord échoué sur leurs PROPRES attentes, pas sur le code : l'un cherchait « opérateur a été prévenu » quand la réponse disait « operateur a ete prevenu » ; l'autre rejouait avec le même pseudo, si bien que la garde de débit refusait le premier appel. Le bon comportement lu comme une panne, deux fois. Un banc qui impose une orthographe mesure une chaîne de caractères, pas un comportement.
Vérifié
APPELéprouvée contre la production depuis un vrai client, sur trois services (OverTchat, OverNick, OverHub) — ce qui vérifie au passage qu'elle est bien commune à tous les rôles. L'annonce arrive sur le salon d'administration en une vingtaine de secondes.- Le bouton du chat cliqué depuis un navigateur : de la souris jusqu'à l'annonce aux opérateurs, avec le bon pseudo et le bon salon.
- Redémarrage réel du serveur :
ExecStartPostdéclenché (status=0), script idempotent — « déjà à jour, rien à écrire » —, 6 connectés revenus, lien ULineactiveet vérifié, MOTD reçu en 52 lignes. - Les deux branches du modèle, y compris celle qu'on ne peut pas voir en production : filtre inactif.
Avertissements
- Le générateur ne s'exécute qu'au démarrage du serveur. C'est le bon moment — le nombre de motifs ne change pas autrement — mais un fichier de motifs vidé sans redémarrage laissera le MOTD affirmer une protection retirée jusqu'au prochain.
- Le filtre lu est celui du SERVEUR, pas celui du site : le premier vaut pour tous les clients, le second ne protège que le chat du navigateur. Les confondre ferait annoncer une protection à qui n'en bénéficie pas.
4.9.2 — 31 juillet 2026
Le chat porte ses règles, et donne enfin de quoi appeler quelqu'un. Un visiteur ordinaire n'avait aucun moyen de signaler un abus : la charte lui disait de « prévenir un opérateur » sans dire par quoi, et le menu de modération est réservé aux gradés — c'est-à-dire à tout le monde sauf à celui qui subit.
Créations
- Trois cadres dans la colonne de gauche du chat : les règles, un avertissement sur les messages privés, un bouton d'appel. Repliés par défaut, présents en permanence.
charte_conduite()etcharte_interdits()— la source unique des règles. La charte et le chat n'en sont plus que deux rendus.chatmod_appel()et la route?p=chat-appel— un appel humain, distinct de l'alerte automatique.Config/apache/maintenance.html— la page servie quand le site ne répond plus, à la place du 503 d'usine d'Apache.
Évolutions
- Le module d'annonce du bot distingue un appel d'une alerte : « appel — X demande un opérateur sur #salon » au lieu de « message reçu par X vers #salon (appel) », qui décrivait un message là où il n'y en a pas.
- Un appel toutes les trois minutes par pseudo, et le refus le dit.
- Le guide : quatre renvois « voir §9 » devenus des liens nommés.
Bugs corrigés
- L'avertissement sur les privés ne ment plus quand le filtre est coupé. Il l'est par défaut ; un cadre l'affirmant sans condition aurait menti à toute installation qui ne l'a pas activé.
Erreurs
- Le site public est resté injoignable une quinzaine de minutes, par ma faute : un
pkill -f 'php -S 127.0.0.1:80'destiné à un serveur d'essai a aussi tuéphp -S 127.0.0.1:8080, celui qui SERT. Le motif était un préfixe, et80préfixe8080. Remis en marche parWeb/start.sh start. C'est le troisième incident de ce dépôt causé par unpkill -ftrop large — les deux précédents avaient tué le shell appelant. Filtrer par PID, ou parpgrep -x.
Vérifié
- Chaîne d'appel éprouvée contre la production : l'appel s'écrit, le bot le relève et l'annonce sur le salon d'administration en dix-neuf secondes, le second appel est refusé par la garde de débit, un GET sur la route rend 405 et un POST sans jeton 400. Les deux alertes d'essai ont été classées.
- Page de maintenance : aucune ressource externe, tient dans l'écran en 1280×800 comme en 375 px, aucun texte sous 11 px.
Avertissements
- La page de maintenance n'est pas encore installée. Elle demande une copie sous
/var/wwwet un rechargement d'Apache, tous deux en root — voir l'en-tête deConfig/apache/irix-ssl.conf.
4.9.1 — 31 juillet 2026
Un guide d'installation pour qui n'a jamais monté de serveur IRC. La page existante est une page de référence : elle suppose qu'on sait ce qu'est un démon, un lien serveur-à-serveur, un paquet de développement. Celle-ci suppose l'inverse, et dit la même chose dans l'ordre où on la rencontre.
Créations
Web/site/guide.php— onze sections, trois schémas, et surtout ce qu'on doit voir après chaque commande. Sans le retour attendu, on ne sait pas si l'étape a réussi, et on enchaîne en emportant l'erreur avec soi.- Un groupe déroulant dans la barre du site — « Installer » réunit le guide, la vue d'ensemble, le téléchargement, les serveurs compatibles et les versions. Bâti sur
<details>, comme le menu replié : il s'ouvre au clavier, sans une ligne de JavaScript, et s'ouvre tout seul quand la page courante est dedans.
Évolutions
- La barre passe de onze entrées à huit plus un groupe. Deux pages qui n'y tenaient pas — « Serveurs compatibles » et le guide — deviennent atteignables autrement que par un lien au milieu d'un texte.
- Le guide nomme trois choses que l'installateur ne fait pas : le serveur MySQL, le compte du panel, et le mot de passe au premier lancement. Les taire produit exactement le genre de blocage qu'on ne sait pas nommer.
- L'accueil mène au guide, dans le bandeau comme au bas de page. Son bouton s'appelait déjà « Guide d'installation détaillé » et menait à la page de référence, qui n'en est pas un ; le libellé cesse de mentir. Le lien « Voir sur GitHub » voisin, qui répondait 404, cède la place au téléchargement.
Bugs corrigés
myoriginvalait le domaine du FAI, et la file de courriel se remplissait toute seule._postfix_configureposaitmyorigin= domaine de l'adresse d'expédition ; avec une adresse chez un fournisseur, la machine prétendait êtrefree.fr. Le courrier local versrootdevenaitroot@free.fr, jugé externe, envoyé sur Internet — et l'aliasroot:posé deux lignes plus bas n'était jamais consulté, les alias ne s'appliquant qu'aux adresses locales. Le garde-fou était court-circuité par le réglage censé le rendre utile. Relevé sur l'installation d'origine : 240 messages en file, un par échec desudo— Debian activemail_badpass—, chacun suivi de son avis de non-remise.myoriginvaut désormais le domaine du site.- Les cinq liens GitHub de la page d'installation menaient à une erreur — page du dépôt, archive zip, fichier « raw », tickets, licence. Le bouton « Télécharger l'archive » tombait sur un 404 à l'étape la plus décourageante qui soit. Seul le protocole git répond sur ce dépôt ; les adresses web non.
Vérifié
- Rendu réel contrôlé au navigateur : les trois schémas se dessinent et héritent des couleurs du site, aucun texte ne déborde de son cadre, le déroulant reste invisible et n'intercepte aucun clic tant qu'il est fermé.
- En 375 px : le groupe se retire de la barre au profit du menu replié, la page ne défile jamais horizontalement, les sept blocs de commandes défilent dans leur cadre sans jamais dépasser la fenêtre, aucun texte n'est sous 12 px et aucune cible tactile sous 32 px.
Avertissements
- « La page ne déborde pas » ne veut pas dire « la page est lisible ». Le premier contrôle mobile ne mesurait que les largeurs, et il était vert : les trois schémas tenaient dans la fenêtre, en s'y réduisant à une échelle de 0,38. Leur texte arrivait à 4,5 px. Il a fallu mesurer la taille rendue, et non la largeur, pour voir ce qu'un coup d'œil aurait vu tout de suite. Les schémas gardent désormais une largeur minimale et leur cadre défile, comme le font déjà les tableaux — défiler pour lire vaut mieux que voir sans lire.
4.9.0 — 31 juillet 2026
La mise à jour cesse d'être un geste manuel, et l'installateur redevient exécutable. Le module de mise à jour existait depuis longtemps — empreintes, sauvegarde, retour arrière — et n'était joignable que par une entrée de menu, donc par quelqu'un devant un clavier. En allant l'ouvrir, on a trouvé pire : le script distribué ne démarrait plus du tout.
Créations
Web/inc/update.php— le service de mise à jour du site, distinct dedistrib.php. Celui-ci répond à « donne-moi ce fichier », celui-là à « suis-je à jour, et qu'est-ce qui a changé ? ». Deux questions qui partagent une racine et rien d'autre./update— une seule requête pour décider : numéro publié, empreinte attendue du script, où le prendre, et l'explicatif tiré de ce journal.- Sept commandes dans l'installateur :
check,update,upgrade,status,history,rollback,version. Aucune n'attend de saisie, et les codes de retour sont ceux qu'attend une tâche planifiée. Web/tests/test-update.php— 50 vérifications.Tools/git-hooks/pre-commit— refuse un fichier que son interpréteur ne sait pas analyser :bash -npour les scripts shell,php -lpour le site et le panel. Il vérifie la version MISE EN SCÈNE et non celle du disque : ce sont deux fichiers différents dès qu'on a mis en scène une partie de ses modifications, et c'est justement là qu'on a le plus besoin du garde-fou. À activer une fois par clone —git config core.hooksPath Tools/git-hooks.
Évolutions
- Le panel annonce les nouvelles versions par une pastille, à côté de celle des demandes de salon. La question part sur le réseau au plus une fois toutes les six heures : le battement du panel, lui, part toutes les quinze secondes depuis chaque page ouverte.
Runtime/version.jsonsert enfin à quelque chose. Il portait déjà les trois champs qu'il fallait et n'était alimenté par rien.- La file d'attente de Postfix se pilote depuis le panel — relancer sans sudo, vider avec. Deux actions distinctes plutôt qu'une avec un paramètre : le niveau exigé se lit sur le nom, et un POST forgé ne peut pas transformer l'une en l'autre.
- Le tableau des liens dit si le serveur du dialecte est installé.
Bugs corrigés
- Trois apostrophes rendaient l'installateur inexécutable. Le JSON par défaut est un argument de
jqentre apostrophes simples ; un commentaire ajouté la veille en portait trois, la chaîne se refermait, et bash lisait le JSON comme du code.bash -nrefusait le fichier entier. Tout téléchargement depuis le 30/07 recevait un script qui sortait sans exécuter une ligne. - La page Statut cherchait deux binaires là où ils ne sont pas. UnrealIRCd est sous
Components/Serveur/bin/, jamais sousServers/; etServers/overtchat-hybridn'est que l'arbre des sources — le serveur EN PRODUCTION s'affichait « binaire absent » pendant qu'il tenait le réseau. - Un port tenu par un autre serveur n'est plus une anomalie. Deux serveurs ne peuvent pas écouter le même port ; que celui qui ne sert pas soit arrêté est la condition du bon fonctionnement. Seul un port ouvert sans que rien n'y corresponde en reste une.
- Le fichier
VERSIONn'était surveillé par rien — sans extension, il échappait au scan, et ne figurait pas dans la liste noyau. Le script se remplaçait, redémarrait, et relisait l'ancien numéro. - Les deux extracteurs de version cherchaient un motif disparu à la 4.0.0 et rendaient une chaîne vide depuis, l'affichage retombant sur une comparaison d'empreintes qui ressemble à une comparaison de versions.
Avertissements
- La règle sudoers n'est pas installée tant qu'on ne l'a pas posée à la main :
sudo install -m 440 Config/sudoers.d/overtchat-services /etc/sudoers.d/. Sans elle, « Vider la file » échoue en disant quoi faire. Celle en service sur la machine est antérieure aux lignes Anope. /updateet/dl/VERSIONne répondent qu'une fois le site déployé. D'ici là,overtchat-setup.sh versionrépond « injoignable » et la pastille du panel reste éteinte — ce qui est le comportement voulu face à une absence de réponse, mais ne prouve rien tant que le déploiement n'est pas fait.- Rien n'est annoncé dans le chat. Le mécanisme existe et fonctionnerait, mais une nouvelle version de l'installateur ne concerne pas les gens qui discutent. Un message répété à un public qui n'y peut rien est du bruit.
- Le hook ne s'active pas tout seul.
core.hooksPathest une configuration locale à chaque clone — et c'est heureux : un dépôt qui imposerait l'exécution d'un script à qui le clone serait un vecteur d'exécution de code. À poser dans le clone WSL comme dans celui de travail. bash -nn'attrape que la SYNTAXE. Il a vu les apostrophes du 30/07, mais il laisse passer unif [ 1 ; then—[est une commande, pas de la grammaire, et la faute n'apparaît qu'à l'exécution. Le hook écarte une classe de pannes, pas les pannes.phpn'est pas installé sur le poste Windows. Le hook y saute la vérification des fichiers PHP et l'ANNONCE — sauter en silence laisserait croire qu'ils ont été contrôlés. Les fichiers PHP du dépôt sont vérifiables depuis WSL, où php 8.4 est présent. ⚠ Le compte écrit ici était faux du double au 25/08/2026 ;git ls-files '*.php' | wc -lle donne.
4.8 1 version · 30 juillet 2026 4 créations 4 évolutions 3 bugs corrigés 6 avertissements
4.8.0 — 30 juillet 2026
Les conversations privées : un endroit pour elles, et de quoi les surveiller sans les lire. Un message privé n'avait pas de fenêtre — il s'affichait dans le salon qu'on regardait et disparaissait au changement d'onglet. Il n'était surtout modéré nulle part, et l'exploration a montré pourquoi : le serveur ne le donne à personne.
Créations
Servers/overtchat-hybrid/src/msgfilter.c— filtre de contenu des messages privés, dans le serveur. Le seul point du réseau qu'on ne contourne pas en changeant de client.Web/inc/chatmod.php— la même analyse côté site, qui fait autorité sur ce qui est enregistré.Web/pages/chat.php— panel : mise en service, journal des alertes, règles visibles.Web/sql/014-chat-prive.sql,IriX/cmds/irix-chatalerte.tcl,Web/tests/test-chatmod.php,tests/test-filtre.py,conf/filtre-messages.example.
Évolutions
- Un onglet par conversation dans le chat, avec compteur de non-lus et fermeture — un salon se rejoint, une conversation s'ouvre. Les services gardent l'onglet « Activité serveur » : leurs réponses ne sont pas une conversation, et les en séparer éloignerait la question de sa réponse.
- La gravité est une somme, non une liste de mots. « J'ai 13 ans » ne vaut rien seul, « envoie-moi une photo » non plus ; ensemble, avec un renfort de combinaison, ils dépassent tous les seuils.
- À l'émission le message est retenu, à la réception il est masqué — affichable d'un clic. L'effacer priverait la personne de ce qu'on lui a écrit, et cela lui appartient.
- L'avis aux opérateurs ne porte jamais le contenu : un salon d'administration est lu par plusieurs personnes et journalisé. Le contenu ne va qu'au service, qui l'enregistre derrière une authentification.
Bugs corrigés
- La conversion leet détruisait la détection d'âge : « 13 ans » devenait « 1e ans ». La règle qui porte tout le dispositif ne voyait plus rien. Le banc l'a trouvé du premier coup ; à l'œil, la ligne semblait inoffensive.
site_reglage_setrend[ok, message]et ne lève pas : le panel aurait annoncé « en service » sur un échec d'écriture.- L'avertissement « aucun salon d'administration » se répétait toutes les vingt secondes — 4 320 lignes par jour pour la même chose.
Avertissements
- Le filtre serveur est inactif tant qu'aucun fichier de motifs n'est déclaré. C'est l'état d'un serveur neuf, et il faut le vouloir pour en sortir. Il est EN SERVICE sur ce réseau depuis le 30/07/2026, 35 motifs.
- Les alertes sont annoncées sur
#!Services!, par deux chemins qui ne se doublent pas : le SERVEUR annonce ses propres refus au moment où il les prononce (réglageFILTER_CHANNEL), et le BOT annonce celles venues du chat du site à son relevé, sous vingt secondes (chanadminen base). Ni l'un ni l'autre ne porte le contenu du message. Le salon est en+O: ces avis ne sont lus que par les opérateurs. #!Services!a dû être AJOUTÉ àIriX_channelspour cela : il n'y figurait pas, unUPDATE … SET chanadmin='oui'n'aurait donc rien posé, et le bot — qui tire de cette table la liste des salons qu'il rejoint — n'était pas sur le salon où on lui demandait d'écrire.- La file d'attente des copies d'alerte vit en mémoire : un arrêt du serveur la perd. Le refus lui-même, qui est ce qui protège quelqu'un, a déjà eu lieu et figure au journal — ce qui se perdrait est la trace consultable, pas la protection.
- Les modes
+G(censure), de salon comme d'utilisateur, restent déclarés, posables, annoncés en 005 — et lus par personne. Ce filtre ne les branche pas : il s'applique à tout le monde ou à personne. tests/test-irix.pyéchoue sur « IriX a reçu notre EOS et clos le burst », et cet échec est ANTÉRIEUR à cette version — vérifié en recompilant le code précédent.
4.7 1 version · 30 juillet 2026 6 créations 3 évolutions 4 bugs corrigés 3 avertissements
4.7.0 — 30 juillet 2026
Une réservation de pseudo qui s'éteint, et un chat qui sait la proposer. Un pseudo réservé l'était pour toujours : le réseau finissait par manquer de pseudos que personne n'utilisait. Il s'éteint désormais après six mois sans identification — mais jamais sans que son titulaire ait été averti, ce qui s'est révélé être la partie la plus délicate à écrire.
Créations
IriX/cmds/irix-nickexpire.tcl— le balayage d'expiration, appelé une fois par jour. Avertissement au cinquième mois, suppression au sixième.IriX/tests/test-nickexpire.tcl— 26 vérifications, éprouvées par six mutations. Ce module supprime des comptes : c'est la partie du projet où un banc vaut le plus cher.IriX/tests/test-sessions.tcl— 30 vérifications sur le lien dont parle chaque écriture de session.Tools/alerte-pseudo.php— l'avertissement par courriel. Son code de sortie décide : sans envoi réussi, rien n'est supprimé.Web/inc/nickservice.php— qui tient les pseudonymes ici, OverNick ou NickServ. Détection, et réglagenick_servicequi l'emporte.Web/inc/reservation.php— l'ouverture d'un compte de site adossé à un pseudo réservé à l'instant.
Évolutions
- La réservation depuis le chat. Le bouton devient « Réserver ce pseudo » quand le pseudo n'est pas enregistré, et le formulaire crée d'un geste la réservation ET le compte du site. La preuve n'est pas un code recopié mais le mot de passe du pseudo — le visiteur est connecté dessus à cet instant.
- Colonne « Réservation » et bouton No-Expire au panel, avec l'échéance en clair : jamais, exempté, sans adresse, ou le nombre de jours restants.
REGISTERgarde l'adresse qu'il annonce, et dit ce qu'il engage.
Bugs corrigés
- Le nettoyage horaire fermait des sessions vivantes. Il juge sur les salons du bot ; un lien serveur-à-serveur voit tout le monde, y compris qui n'y est pas. À la première heure ronde, ces sessions étaient closes « orphelines » alors que ces gens étaient là — et sans
LIMIT, toutes celles du pseudonyme, sur tous les liens. - Personne ne fermait les sessions d'un lien tombé : elles restaient ouvertes jusqu'au redémarrage suivant.
- Six réponses WHO/WHOIS écrivaient dans la session d'un homonyme connecté sur un autre lien.
svc_config.namen'est pas le rôle : la lecture du pseudo-client cherchaitname = 'nick', ne trouvait jamais rien et retombait sur un repli — juste ici par coïncidence, faux dès qu'un service est renommé.
Avertissements
- L'envoi SMTP n'a jamais été éprouvé avec une vraie adresse. Tant qu'il échoue, aucune réservation n'est supprimée : c'est le bon sens de l'échec, mais la fonction reste inerte jusqu'à sa première vérification.
- Les pseudos sans adresse ne sont jamais supprimés automatiquement — on ne peut pas les avertir. Le panel les signale pour décision humaine.
- Le bouton du chat n'a pas pu être vu à l'œuvre : le tunnel WebSocket n'est pas joignable depuis l'environnement de vérification. Ce qui est vérifié : la page sert la bonne configuration, le fichier déployé contient bien la logique, et l'ouverture de compte refuse le GET comme le POST sans jeton.
4.6 14 versions · 28 juillet 2026 → 30 juillet 2026 12 créations 55 évolutions 66 bugs corrigés 6 suppressions 13 constats 15 erreurs 36 avertissements
4.6.13 — 30 juillet 2026
Quatre dialectes, quatre liens, et tout ce que trois liens simultanés ont révélé. La version précédente fermait le lot 7 sur un lien p10 qui sert. Celle-ci monte les trois autres, ajoute au panel une page qui compare le déclaré au réel — et paie le prix de la nouveauté : la moitié de ce qui suit est une correction de choses qu'un seul lien n'avait jamais mises en défaut.
Évolutions
- Un socket par dialecte, chacun vérifié contre un vrai serveur. ircd-Hybrid 8.2.47 a été configuré et lancé pour l'occasion ;
unreala été éprouvé par bascule temporaire, faute de pouvoir coexister avec la production.
| Socket | Dialecte | Serveur | État |
|---|---|---|---|
| 1 — Overtchat | och | Overtchat-Hybrid, 127.0.0.1:6900 | production |
| 3 — Hybrid-essai | hybrid | ircd-Hybrid 8.2.47, 127.0.0.1:7202 | essai |
| 4 — ircu-essai | p10 | ircu 2.10.12.19, 127.0.0.1:7102 | essai |
| 5 — Unreal-essai | unreal | Overtchat-Hybrid, 127.0.0.1:6900 | éprouvé, puis désactivé |
- Le dialecte
hybrida monté du premier coup, opérateur du salon compris : le correctifEOBde la veille, écrit sans serveur en face, était le bon. C'est le serveur lui-même qui a résisté — sansmodules.conf, ircd-Hybrid ne charge AUCUN module, ne connaît donc ni PASS, ni CAPAB, ni SERVER, ne répond rien, et referme au bout de trente secondes sur « Registration timed out » sans jamais dire qu'il n'a pas compris. - La clé de
${t}_onlinepasse à(socket_id, uid). Un UID n'est unique que DANS SON LIEN, et deux réseaux choisissent leurs identifiants de serveur chacun de leur côté. ${t}_sessionsporte une colonnesocket_id. Entre réseaux liés simultanément, l'homonymie n'est pas un cas limite : c'est la règle.- Dix-sept fichiers
.mdrecalés sur l'état réel du projet. - Le site public dit enfin sur quoi ce programme s'installe. Jusqu'ici, la page d'installation supposait le serveur maison ; elle renvoie désormais vers une page qui compare les quatre, fonction par fonction et protection par protection. Son tableau des capacités est lu dans la base du bot, celle qu'il republie à chaque démarrage — avec la provenance affichée, et un instantané daté en repli quand la base ne répond pas. Une page de compatibilité qui vieillit sans le dire est pire que pas de page du tout : quelqu'un installe sur la foi d'une ligne périmée.
Créations
| Fichier | Rôle |
|---|---|
Web/inc/statut.php | La logique de la page « Statut serveurs » : lecture des déclarations, observation de la machine, et une fonction pure de verdict |
Web/pages/statut.php | La page elle-même — lecture seule, cinq verdicts, messages d'erreur / avertissement / information |
Web/tests/test-statut.php | 52 vérifications sans base, sans fichier et sans réseau |
Web/inc/compat.php | Les quatre serveurs, ce que chaque protocole permet, et ce que la sécurité y gagne ou y perd |
Web/site/compatibilite.php | La page publique correspondante — ?p=compatibilite, quatre tableaux, une croix pour l'impossible et une flèche pour le reste |
Web/tests/test-compat.php | 169 vérifications, dont celle qui compte : un refus doit être motivé, jamais une case barrée sans raison |
IriX/cmds/irix-proto.tcl → sid_de_uid | Le serveur d'origine d'un client, dont la forme dépend du dialecte |
Bugs corrigés
- Aucune session n'était plus ouverte à l'arrivée d'un client.
::soc::online::on_uidappelaitsession_openavec$vhost, variable supprimée le matin même en passant la lecture au dialecte. Tcl levait « no such variable », lecatchl'avalait. Une régression introduite et refermée le même jour, mais qui dit tout : uncatchsans journal est un endroit où les défauts s'installent — celui-ci en journalise désormais l'échec. - L'installateur ne téléchargeait pas
irix-proto.tcl, queIriX-sql.tclsource sans garde depuis le lot 3 : une installation neuve serait morte au chargement. Le site le distribuait pourtant ; c'est la liste embarquée dansBootstrap/overtchat-setup.shqui l'avait oublié. Troisième divergence de ce manifeste en un mois — rien ne le compare automatiquement auxsourceréellement écrits. - Le serveur d'origine d'un client était faux d'un caractère sur un lien p10 :
serveur_deprenait trois caractères en dur, la forme d'UnrealIRCd, et attribuait « AqA » à un client du serveur « Aq ». - Le tampon de la table des connectés confondait deux liens : indexé par UID seul, comme les
DELETEet lesUPDATEpartiels qui l'accompagnent. Le départ d'un client effaçait la ligne d'un homonyme d'ailleurs. purge_socketvidait le tampon entier : la chute d'un lien d'essai jetait au passage les changements en attente de la production.session_closefermait la session du premier homonyme trouvé, tous liens confondus, et le rattrapage d'adresse écrivait celle de l'un dans la session de l'autre.- La page de statut elle-même a rendu trois faux verdicts à son premier affichage :
ROOTest défini en dur sur l'installation qui sert, et déduire la racine du chemin du fichier faisait voir « non installé » sous deux serveurs qui tournent.
Erreurs
- Un commentaire qui contredisait son propre fichier : la section p10 affirmait encore que
decodablesne contenait pasp10, deux mille lignes plus bas que la ligne qui l'y inscrivait. - Quatre affirmations fausses dans la couche de protocole, relevées en revérifiant chaque référence contre les sources :
SJ/SVSNICK dans la table des jetons d'un serveur qui n'a ni l'un ni l'autre, le mode+rde P10 donné pour inexistant, le+Rde salon aussi, et la table des modes d'ircd-Hybrid amputée dew— un+wdemandé était donc écarté avec un motif faux. - Les comptes de la documentation étaient faux depuis le lot 3 : quinze modules Tcl annoncés au lieu de seize, quatorze documents au lieu de quinze, et
PROTOCOLES.md— le document des huit lots — n'était indexé nulle part. BACKUP.mdjustifiait l'exclusion deServers/par « versionnés dans le dépôt ». Ils ne le sont pas : les configurations des deux serveurs d'essai n'existent que sur le disque de cette machine.- Deux défauts des bancs eux-mêmes : un constat qui cherchait « 501 » dans une sortie contenant son propre libellé, et un client témoin nommé en dur qui faisait échouer tout second essai enchaîné.
Vérifié
332 vérifications hors lien côté Tcl, 52 côté PHP, et surtout des preuves par l'effet, sur les liens réels :
- une session réellement écrite à l'arrivée d'un client, lue pendant qu'il est connecté — après la déconnexion, on ne verrait que le
quitd'unUPDATE, sans savoir si l'ouverture a eu lieu ; - trois clients du même pseudonyme sur les trois réseaux, chacun sous son lien et avec les champs de son dialecte, et le départ de l'un qui n'emporte rien chez les autres ;
- sur ircu comme sur ircd-Hybrid, un client ordinaire voit les cinq pseudo-clients opérateurs de
#!Services!— le@vient du serveur, pas de notre journal.
Chaque correction a été mise en défaut avant d'être crue : le défaut remis en place, aucune session n'est écrite (490 → 490) ; restauré, les deux s'ouvrent. Un banc qui reste vert sous mutation ne mesure rien.
Avertissements
- Les sockets 1 et 5 ne peuvent pas être montés ensemble : le serveur n'a qu'un bloc de lien pour ce nom, et vérifie le SID annoncé.
- Les serveurs d'essai vivent hors du dépôt et hors des sauvegardes.
Servers/*est gitignoré saufovertchat-hybrid/. - Les sessions antérieures à cette version portent
socket_id = 0, qui se lit « lien inconnu » : cette table est un historique, elle n'est pas vidée au démarrage. — levé le jour même : la fonction, ses compteurs et la gardeonline_list()ne filtre pas sursocket_idonline_oper_present()prennent un lien facultatif, la page Connectés gagne un sélecteur et un badge d'origine. Deux défauts plus graves sont sortis de là : un tableau d'arguments SQL écrasé au lieu d'être complété, et_oper_presentcôté bot qui s'arrêtait au premier homonyme — un pseudonyme sans+osur un lien d'essai faisait refuser l'action d'un opérateur bien réel de la production, une fois sur deux, sans motif lisible.- p10 n'a encore servi que des pseudo-clients non rattachés en base.
4.6.12 — 30 juillet 2026
Le lien p10 ne servait à rien, et le banc disait que tout allait bien. La veille au soir, 241 vérifications passaient pendant qu'ircu refusait cinq modes, annulait cinq fois l'op et laissait la sonde sans réponse. Six écarts, tous nommés par le serveur dans des lignes qu'IriX jetait dans son default. Ce qui ferme le lot 7 n'est pas le code : c'est le lien qui sert, et un client témoin qui le confirme.
Évolutions
- Les modes de service se traduisent par RÔLE, pas par lettre. Recopier les lettres d'un serveur à l'autre donne des modes acceptés et faux :
qest PROTECTED sous UnrealIRCd et HIDEIDLE sous ircd-Hybrid,ds'y écritD, etkest SKILL chez lui quand il est CHSERV sous ircu.+iSdevient donc+iken p10,+isur ircd-Hybrid — qui n'a AUCUN mode de service —, et ce qui est écarté est dit dans le journal, jamais perdu en silence. - Quatre primitives de plus dans la façade :
modes_service,est_introduction,lire_burst_salon,decoder_ip. Etlire_clientrend cinq champs de plus (ident, hôte, hôte affiché, adresse, nom réel). ::soc::online::on_uidne lit plus par index en dur. Il remplit la table des connectés du panel et lisait aux positions d'UnrealIRCd : sur un lien d'un autre dialecte, les colonnesuid,ident,host,vhostetipauraient été fausses, lecatchde l'appelant avalant tout. Il passe parlire_client, etdecode_ipdélègue àdecoder_ipdu dialecte.p10entre dans::soc::proto::decodables— la liste que le lot 7 avait créée précisément pour empêcher de l'y mettre trop tôt. Les quatre dialectes sont désormais utilisables, et la table des capacités en publie douze fonctions chacun, soit 48 lignes.
Créations
| Fichier | Rôle |
|---|---|
IriX/tests/lien-p10.tcl | Monte un vrai lien contre l'ircu de Servers/ircu avec les procédures réelles, extraites de IriX-sql.tcl — pas un simulateur |
IriX/tests/essai-p10.py | Fait juger ce lien par un client témoin connecté au port utilisateur : dix constats, dont le @ devant les cinq services dans son NAMES |
Bugs corrigés
- Les numériques de réponse n'étaient pas des verbes.
decoupercherchait un verbe connu en second mot ;318n'en étant pas un, « Aq 318 … » se lisait sans préfixe avec « AQ » pour commande. Aucune branche311/318/401du dispatcher n'était atteignable : la sonde de présence ne POUVAIT pas aboutir, quelle qu'eût été la forme du WHOIS envoyé. - Le dialecte était résolu APRÈS le SID, dont la forme est justement une affaire de dialecte. Les deux gardes interrogeaient donc le dialecte de repli, et un socket p10 sans SID en base recevait un « 002 » d'UnrealIRCd. Le lien montait quand même — ircu lit les deux premiers caractères et prend le reste pour de la capacité — puis tuait les cinq pseudo-clients : « Unknown numeric nick ». Invisible en production, où la variable gardait la valeur d'une connexion précédente.
- L'op se prenait par
JpuisM +o, et ircu annulait les cinq :ms_joindéope systématiquement, et unMvenant d'un client non opérateur est renvoyé en déopant sa source. C'estC(CREATE) qui donne l'op — et les cinq services doivent partager le TS de l'injection, faute de quoi seul le premier l'obtient et les quatre autres déclenchent un avis de désynchronisation. - La sonde WHOIS nommait un serveur :
ms_whoistraite son premier argument comme tel dès qu'il y en a deux, d'où le « 402 :Server has disconnected ». - L'adresse restait
AAAAAA:client_entrelisait un champ que personne ne remplit. Elle encode maintenant l'adresse brute, et ne touche jamais au champip64, dont le nom ne dit pas pour quel serveur il a été encodé. - Un lien
hybridn'aurait jamais reconnu la fin de son burst :EOBmanquait à la liste des verbes de fin. Même défaut que celui d'EBpour ircu, corrigé la veille — relevé en relecture, avant qu'un serveur ne le montre. - L'installateur ne téléchargeait pas
irix-proto.tcl, queIriX-sql.tclsource sans garde depuis le lot 3 : une installation neuve serait morte au chargement. Le site le distribuait pourtant — c'est la liste embarquée dansBootstrap/overtchat-setup.shqui l'avait oublié.
Erreurs
- Un commentaire qui contredisait son propre fichier : la section p10 affirmait encore que «
decodablesne contient PAS p10 » et que « le panel refuse de basculer un lien vers ce dialecte », deux mille lignes plus bas que la ligne qui l'y inscrivait. - Trois affirmations fausses, relevées en revérifiant chaque référence contre les sources :
SJ/SVSNICK figurait dans la table des jetons d'un serveur qui n'a ni l'un ni l'autre — et_est_verbel'acceptait —, le mode+rde P10 était donné pour inexistant alors que c'estFLAG_ACCOUNTdont seul le retrait est impossible, et le+Rde salon existe aussi. Une quatrième était incomplète : la table des modes d'ircd-Hybrid omettaitw, si bien qu'un+wdemandé était écarté avec un motif faux. - Deux défauts des bancs eux-mêmes, trouvés en les faisant tourner : un constat qui cherchait « 501 » dans une sortie contenant son propre libellé « modes refusés (501) » — donc toujours rouge —, et un témoin nommé « Temoin » en dur, qui faisait échouer tout second essai enchaîné sans dire pourquoi.
Vérifié
327 vérifications hors lien, 0 échec, et dix constats sur un lien réel. Chacun des deux bancs a été mis en défaut par mutation du code — remettre J suivi de M +o fait tomber l'op à 0 sur 5, retirer la traduction ramène les 501, la sonde à deux arguments empêche l'état « active ». Un banc qui reste vert sous mutation ne mesure rien.
Les quatre dialectes ont parlé à un vrai serveur, chacun par son socket :
| Socket | Dialecte | Serveur | État |
|---|---|---|---|
| 1 — Overtchat | och | Overtchat-Hybrid, 127.0.0.1:6900 | production, active + verified |
| 3 — Hybrid-essai | hybrid | ircd-Hybrid 8.2.47, 127.0.0.1:7202 | essai, active + verified |
| 4 — ircu-essai | p10 | ircu 2.10.12.19, 127.0.0.1:7102 | essai, active + verified |
| 5 — Unreal-essai | unreal | Overtchat-Hybrid, 127.0.0.1:6900 | éprouvé par bascule, désactivé au repos |
Sur ircu comme sur ircd-Hybrid, un client ordinaire voit les cinq pseudo-clients opérateurs de #!Services! — le @ vient du serveur, pas de notre journal.
Avertissements
- Les sockets 1 et 5 ne peuvent pas être montés ensemble : Overtchat-Hybrid n'a qu'un bloc de lien pour « IriX.overtchat.fr », et il vérifie que le SID annoncé est celui qu'il a configuré. Le socket 5 reste donc désactivé au repos.
- Les deux serveurs d'essai vivent hors du dépôt.
Servers/*est gitignoré saufovertchat-hybrid/: les configurations d'ircu et d'ircd-Hybrid, y compris les réglages qu'il a fallu leur faire, n'existent que sur le disque de cette machine. - p10 n'a encore servi que des services non rattachés en base : sur le socket 4,
svc_confign'en porte aucun, si bien que l'injection passe par la branche de repli qui litIriX-src.conf.
4.6.11 — 30 juillet 2026
Le décodeur appartient enfin au dialecte. Il était écrit en dur dans le tronc commun, et supposait trois choses vraies de tous les serveurs rencontrés jusqu'ici : un deux-points en tête de préfixe, des verbes en toutes lettres, un identifiant d'une seule forme. P10 rompt les trois. C'est la seconde moitié du lot 7, et le seul endroit où ce lot touche le chemin qui sert.
Évolutions
- Cinq lectures rejoignent la couche :
decouper,generer_uid,identifiant_valide,encoder_ip,lire_client. Le tronc commun n'a plus une seule branche par serveur — la règle du §3, « par délégation, jamais par condition en ligne », est tenue. decouperrend le verbe en forme longue, ce qui a évité de réécrire le dispatcher : sonswitchreste en toutes lettres face à un serveur qui n'envoie que des jetons d'une lettre. Aucune de ses branches n'a changé.- Reconnaître un préfixe sans deux-points se fait sur le SECOND mot : le jeton
PAest aussi un numérique de serveur valide, et se fier au premier ferait lire « PA :secret » comme un préfixe. lire_clientcompte depuis la fin en P10 — les modes y sont facultatifs. Compter depuis la gauche donnerait le numérique pour des modes une fois sur deux.
Bugs corrigés
- Le banc de non-régression ne mesurait pas la normalisation des verbes. En retirant le
string toupperdedecouper, il restait vert : ses neuf lignes d'essai portaient toutes un verbe déjà en majuscules. Deux lignes à verbe minuscule et une vérification directe ont été ajoutées.
Vérifié
241 vérifications, 0 échec. La seule qui compte vraiment sur ce chemin : l'ancien corps du dispatcher est recopié dans le banc et comparé au nouveau sur onze lignes réellement vues sur le lien de production. Idem pour l'ancien générateur d'UID sur sept valeurs dont les bornes de base 36, et pour l'ancien encodage d'adresse, replis compris.
Avertissements
Ces trois avertissements ont été levés le jour même, en 4.6.12 — ils sont laissés tels quels : ils disent ce qu'on savait ne pas savoir, et c'est ce qui a rendu la suite possible.
p10n'est toujours pas dansdecodables, et c'est délibéré : rien de ce décodeur n'a rencontré un vrai serveur ircu. Un banc prouve ce qu'on compose et ce qu'on découpe, pas qu'un lien monte. L'inscription attend un socket d'essai, le burst lu et un message échangé.- IriX ne relira ces fichiers qu'à son prochain rechargement. Jusque-là, la production tourne sur le code d'avant.
- Restent le burst de salon
B, dont la lecture n'est pas écrite, et le calcul de l'IP en base64 d'ircu là où la fiche du service est construite —encoder_ipsait le faire, il reste à l'y brancher.
4.6.10 — 30 juillet 2026
Un encodeur P10, et la notion qui empêche de s'en servir trop tôt. Le dialecte p10 est écrit : 24 primitives pour ircu 2.10.12.19. Mais P10 n'est pas un dialecte de plus — c'est un autre protocole —, et le décodeur d'IriX n'en lit pas un mot. C'est le lot 7, à moitié.
Créations
::soc::proto::p10— ~340 lignes, sur les sources compilées le jour même.::soc::proto::decodables,decodable,utilisable— et la colonneutilisablepubliée en base, lue parproto_utilisables()côté panel.
Évolutions
- Écrit ne veut plus dire utilisable.
dialectesse déduit des namespaces qui portent des primitives : c'est une question d'encodeur.decodablesest une liste tenue à la main, à dessein — elle dit ce que le décodeur sait lire, ce qui ne se déduit d'aucun namespace. Le panel refuse la bascule vers un dialecte non utilisable, et dit pourquoi. - Six primitives ne sont pas définies pour p10, et c'est le résultat du lot : ni
renommer, nifaire_entrer, nifaire_sortir, nichanger_hote, nichanger_ident, nireserver_aux_operateurs. C'est ce qui empêcheirix-operjoinetirix-nickguardde croire qu'ils protègent quelque chose sur ce serveur. Une septième,marquer_identifie, refuse la dépose :ACpose un compte, et seule la déconnexion l'efface.
Bugs corrigés
- Une vérification du banc mesurait l'inverse de ce qu'elle annonçait. « Un dialecte non écrit retombe sur unreal » employait
p10comme exemple — orp10est écrit depuis ce jour. Le repère porte désormais sur un dialecte franchement imaginaire.
Vérifié
201 vérifications hors lien, 0 échec — 52 ajoutées, chaque ligne comparée octet à octet aux formes du serveur, refus compris. Deux mutations pour éprouver le banc : un deux-points ajouté en tête d'une ligne P10 en fait tomber 1, le + retiré d'une G-line en fait tomber 3.
Trois pièges relevés dans les sources, qu'aucune documentation ne donne : les modes de la ligne N emportent leur espace ou disparaissent entièrement, le statut d'opérateur ne voyage pas dans le J — il faut deux lignes —, et la cible d'un I est un pseudonyme là où tout le reste emploie des numériques.
Avertissements
- Le décodeur n'est pas fait, et c'est l'essentiel du travail restant : découpe de ligne sans deux-points, identifiants base64 sensibles à la casse, table des verbes en réception. Sans lui, un lien
p10monterait et ne transporterait rien. - L'encodage des IP en base64 d'ircu manque aussi côté émission :
client_entreécritAAAAAA, la valeur qu'ircu emploie lui-même pour une adresse inconnue. - Tant qu'IriX n'a pas redémarré, le panel voit
hybridetp10comme non écrits — la table des capacités est republiée au démarrage. C'est le comportement sûr : aucun des deux n'est proposé entre-temps.
4.6.9 — 30 juillet 2026
IriX sait parler à un second serveur. Le dialecte hybrid est écrit : 29 primitives pour ircd-Hybrid 8.2.47, chacune fondée sur une ligne de ses sources. C'est le lot 6 de PROTOCOLES.md, celui dont le préalable n'était pas du code mais une machine.
Créations
::soc::proto::hybrid— ~330 lignes. Chaque primitive porte en commentaire le fichier et la ligne dont sa forme est tirée. Rien n'est déduit du « TS6 en général », qui n'existe pas : chaque implémentation a ses écarts, et ce sont eux qui font tomber un lien.
Évolutions
- Presque rien ne se délègue à
unreal, contrairement àoch. Les deux serveurs ne partagent que les commandes les plus banales, et jusqu'à celles-là la source diffère. Une délégation aurait donné un dialecte qui a l'air de marcher et déraille au premier cas particulier. - Le handshake n'est pas le TS6 « classique » : le SID voyage dans
SERVERetPASSne porte que le mot de passe.CAPABn'annonce qu'EOB— une capacité annoncée est une promesse de lire, et il ne faut pas réclamer ce qu'on ne décode pas. - Le TS à zéro désactive le contrôle des commandes de service : le serveur écrit
if (ts && ts != target->tsinfo) return. C'est la porte qu'il laisse ouverte, et c'est elle qui permet aux primitives de garder la signature de la façade, laquelle ne transporte pas cet horodatage. MODEplutôt queTMODE:TMODEcontrôle le TS du salon, qu'on n'a pas, et un TS inventé ferait échouer la commande en silence.MODEa unSERVER_HANDLERqui n'exige rien de la source.changer_identn'est pas défini, et c'est le travail : ircd-Hybrid n'a aucun équivalent deCHGIDENT. La façade lève une erreur nommant le dialecte, plutôt que d'émettre une ligne qui ne ferait rien.
Vérifié
149 vérifications hors lien, 0 échec — 49 ajoutées pour ce dialecte, chaque ligne comparée octet à octet à ce que le code du serveur dit attendre, refus compris. Le banc a été éprouvé par deux mutations : l'ordre des champs du UID inversé en fait tomber 2, le TS de SVSKILL retiré en fait tomber 1.
Avertissements
- Aucun lien n'a été monté. Un banc hors lien ne dit rien de la réaction du serveur, seulement de ce que nous composons. L'épreuve se fera sur un socket déclaré au panel — jamais sur celui qui sert.
- Un point du dialecte n'a pas été établi par lecture : le TS de salon d'
INVITE, laissé à 0 faute d'être transporté par la façade. Rien dans le code lu ne dit qu'un zéro y vaut « ne pas vérifier », contrairement aux commandes de service. C'est le seul endroit à éprouver avant de s'y fier. - Le panel proposera désormais
hybriddans le choix de dialecte d'un lien, puisque les dialectes « écrits » sont déduits des namespaces qui portent des primitives. Écrit ne veut pas dire éprouvé.
4.6.8 — 30 juillet 2026
Trois serveurs et un logiciel de services, installés pour de bon. Le projet raisonnait depuis des semaines sur des serveurs qu'il n'avait jamais compilés. PROTOCOLES.md en portait la trace : six questions sans réponse, et une table de capacités où neuf lignes sur onze disaient « non — non confirmé, aucun binaire installé pour le vérifier ». Ils sont installés. Huit de ces neuf « non » étaient faux.
Évolutions
- Cinq sources épinglées, en un seul endroit — en tête de
overtchat-setup.sh. Les numéros vivaient à trois : le JSON des serveurs, celui des services, et un--branch v1.10.1au milieu d'une fonction. Relevés le 30/07/2026 par code HTTP, taille et tags d'API, pas recopiés. _sources_syncaligne les JSON déjà écrits. Sans elle l'épinglage n'aurait valu que pour une installation neuve : ces fichiers ne sont pas suivis par git, et les_initne réécrivent jamais un fichier existant.- Le site lit les versions au lieu de les recopier —
composants_liste()extrait les constantes du script. La page annonçait « v3.5.2 » face à un dépôt en 4.6.7 ; c'était la troisième fois qu'un numéro écrit en dur mentait.
Ce qui est installé, et ce que ça a appris
- ircd-Hybrid 8.2.47 — sept modules
m_svs*, un mode+rréservé aux services, un+Rde salon qui filtre bien dessus,+O, les cinq grades+q +a +o +h +v, etGLOBOPS. SeulCHGIDENTmanque vraiment. Les questions 2, 3 et 4 du §8 sont tranchées, la 6 pour moitié. - ircu2 u2.10.12.19 — sa source était une page HTML que rien ne pouvait télécharger : l'installation ne pouvait pas réussir.
- Anope 2.1.25 — et avec lui la question 5, ouverte depuis le lot 8 :
modules/protocol/contient bienunrealircd.cpp, et aucun module P10. Le refus opposé au dialectep10est désormais fondé sur la source.
Bugs corrigés
- Un
install_dirnul, et 876 Mo compilés dans un dossier nommé « null ». Leservices.jsonen service vient d'un schéma antérieur qui n'avait pas ce champ. Rien ne le vérifiait :mkdir,tar,cmakeetmakeont travaillé sur un chemin relatif, dans le dépôt lui-même._dir_exploitablerefuse désormais un chemin vide, « null » ou relatif ;_svc_repair_dirsrépare le fichier. - Deux binaires que personne ne cherchait sous leur vrai nom. Anope 2.1 produit
bin/anopeet nonbin/services: l'installation échouait sur « binaire introuvable » après une compilation réussie, puis le rollback effaçait tout. Overtchat-Hybrid produitbin/overtchatd, absent lui aussi de la liste. - Deux
source_urlmorts : une page HTML pour ircu, un compte GitHub abandonné pour Overtchat-Hybrid — dont les sources sont dans le projet. - Trois paquets exigés pour rien.
_install_hybridréclamait autoconf, automake et libtool avant même de regarder les sources, qui portent leurconfigure. Sur une machine sans élévation, l'échec était définitif.
Erreurs
J'ai écrit dans un commit que l'archive d'un tag GitHub d'ircu2 n'embarquait pas de configure. C'était une supposition présentée comme un fait ; elle est fausse, et le commit 6b34d04 la rectifie. J'ai aussi annoncé que le nom horodaté du binaire d'ircu allait faire échouer _srv_finalize : make install crée lui-même le lien.
Avertissements
services.confd'Anope n'est pas écrit._anope_configurelit le mot de passe du lien au clavier, et ce secret n'a pas transité par cette session. La configuration reste à faire.- Aucun de ces trois serveurs n'a été démarré. Ils sont compilés et installés ; qu'ils montent un lien avec IriX reste à éprouver — c'est l'objet des lots 6 et 7.
- Eggdrop est épinglé sur une version candidate (1.10.2rc1), par décision explicite. La dernière version publiée est la 1.10.1, et IriX tourne dessus.
4.6.7 — 30 juillet 2026
Le panel Anope savait allumer sept pseudo-clients, pas dire à quel serveur ils parlent. Anope ne devine pas le dialecte de son lien : un bloc module le désigne, et un module faux ne produit aucune erreur franche — Anope démarre, puis le lien échoue au burst. Le panel pilotait les sept pseudo-clients sans jamais regarder ce bloc-là. C'est le lot 8 de PROTOCOLES.md, et le dernier atteignable sans installer de serveur tiers.
Créations
Web/tests/test-anope.php— le premier banc PHP du projet, 62 vérifications sur desservices.confde synthèse. Anope n'est installé sur aucune machine d'ici : il n'y a pas de fichier réel à lire, pas de lien à monter, aucune observation possible en service. Une preuve hors installation était donc la seule disponible, et elle a décidé de la forme du code.
Évolutions
- La décision est une fonction pure —
anope_protocole_verdict(lignes, dialecte). Tout ce qui choisit est dedans ; ce qui l'entoure ne fait que lire un fichier et une table. Enfouie dans la lecture, cette décision n'aurait pu être éprouvée d'aucune manière. - Le dialecte n'est pas un choix de cette page : il vient de la colonne
protodu lien ULine, et le POST ne le porte pas. Un formulaire qui l'accepterait permettrait de configurer Anope pour un serveur qui n'est pas celui d'en face — exactement la panne que ce lot doit rendre impossible. - Cinq verdicts qui ne se confondent pas : conforme, à activer, non déclaré, divergent, sans objet. Le divergent est le coûteux — un module de protocole étranger actif — et il a sa propre couleur.
- P10 refuse, et le dit : Anope n'embarque aucun module P10, ni en 2.0 ni en 2.1. Ce n'est pas une configuration à corriger, c'est un logiciel de services à changer — srvx, X3 ou GNUWorld. Le refus tient même quand un module de protocole est actif, puisque c'est justement le cas où Anope tourne sur un réseau qu'il ne sait pas servir.
- Une seule écriture, jamais deux. Éteindre le module étranger et allumer le bon se fait en mémoire puis s'écrit en une fois : aucun état intermédiaire du fichier ne porte deux protocoles actifs, ou aucun.
Ce qui n'est pas fait, et pourquoi
- Aucun rechargement n'est demandé après le réglage. Un module de protocole se choisit au démarrage, et rien ici ne permet d'affirmer qu'un
SIGHUPrebrancherait un lien déjà établi. Le message dit qu'il faut redémarrer et laisse ce geste — qui déconnecte tous les pseudo-clients — à l'exploitant, plutôt que d'annoncer une prise d'effet qui n'aurait pas eu lieu. - Le nom du module UnrealIRCd reste inconnu (question 5 du §8). Il diffère entre Anope 2.0 (
unreal) et 2.1 (unrealircd). La question n'est pas tranchée : elle est contournée. Le panel lit lequel des deuxservices.confdéclare, et ne retient l'ordre de sa table que si le fichier n'en déclare aucun — auquel cas il affiche « non déclaré » et refuse d'écrire, plutôt que d'inventer un bloc.
Vérifié
62 vérifications hors installation, dont l'aller-retour complet de la bascule et celle qui compte le plus : après application, le mot de passe du lien et tout le reste du fichier sont intacts — le panel commente et décommente, il ne réécrit pas.
Et le banc a été éprouvé lui-même, par deux mutations du code : l'ordre des opérations inversé fait tomber 1 vérification, le choix du nom figé sur le premier candidat en fait tomber 4. Un banc qui reste vert sous mutation ne mesure rien — c'est la leçon du 29/07, appliquée avant d'annoncer un résultat.
Avertissements
Ce qui n'est pas prouvé : qu'Anope, ce module chargé, monte le lien. Il faudra l'installer pour le savoir. La liste des noms de modules de protocole reconnus n'a été lue dans aucune installation — elle peut être incomplète, et un nom qu'elle ignore passera pour un module ordinaire. C'est le sens prudent : le panel signale ce qu'il reconnaît et se tait sur le reste.
4.6.6 — 29 juillet 2026
Le lien de production dit enfin à quoi il parle. Il annonçait unreal ; il a en face Overtchat-Hybrid. Les deux dialectes émettent la même chose sur tout ce qu'IriX utilise — mais pas les mêmes possibilités, et c'est ce que le panel affichait de travers. C'est le lot 5 de PROTOCOLES.md.
Évolutions
- Dialecte
och, qui hérite d'unrealpar délégation explicite, une procédure par primitive. Plus verbeux qu'un relais automatique, et voulu : une primitive ajoutée àunrealn'apparaîtra pas ici toute seule, elle demandera le geste de vérifier qu'Overtchat-Hybrid la comprend. Un héritage muet promettrait, au nom de ce serveur, tout ce qu'on écrirait pour l'autre. - Le routage par lien, annoncé au lot 4 comme non fait : 46 compositions passent désormais par
::soc::proto::pour <lien>. La colonneprotopilote donc la composition, et plus seulement l'acceptation du lien. Pas de « lien courant » global : un point d'entrée oublié aurait composé dans le dialecte d'un autre lien, sans un mot. - Le lien de production est déclaré
och.
Bugs corrigés
- Une composition d'
UIDécrite en clair subsistait dansIriX-sql-fixes.tcl, oubliée par le lot 3 : son argument passait à la ligne, et le relevé des sites ne cherchait que les appels tenant sur une seule. irix-proto.tclne se chargeait que si quelqu'un avait préparé le terrain : il définissait des procédures dans des namespaces déclarés ailleurs. La campagnetest-irix.py, qui le source seul, l'a révélé en refusant de créer le premier dialecteoch. Le fichier déclare maintenant ce qu'il emploie — sans effet en production, indispensable partout ailleurs.
Vérifié
100 vérifications hors lien — dont, primitive par primitive, que och rend exactement les mêmes lignes qu'unreal sur tout ce qu'IriX émet. 26 contre un vrai serveur. Et la mesure qui autorisait la bascule : le burst d'avant et celui d'après, 13 lignes identiques octet pour octet, puis les 17 vérifications de bout en bout rejouées sous le nouveau dialecte.
Ce que la vérification a démenti
Deux des six écarts annoncés par le document étaient déjà traités : IriX lit les changements de mode utilisateur sous les deux formes, UMODE2 et MODE <uid> :<modes>, et le serveur accepte ~ comme * pour le propriétaire. Le code était en avance sur sa propre documentation — une raison de plus de vérifier plutôt que de recopier.
4.6.5 — 29 juillet 2026
Une session comptait les salons rejoints, pas les connexions. L'onglet Connexions du panel promet « qui est venu, quand, d'où, et quand il est reparti » ; il montrait en réalité une ligne par entrée dans un salon où le bot siège. Seize pour IriX en deux heures.
Bugs corrigés
- Une arrivée, une ligne. La session s'ouvrait sur le bind
JOINd'Eggdrop et se fermait surPART— donc en quittant un salon, alors que la personne est toujours sur le réseau. L'ouverture suit désormais l'arrivée sur le réseau (online::on_uid), qui la voit une seule fois et complètement : ident, hôte, hôte masqué, adresse et serveur d'origine sont tous dans la commandeUID. La fermeture suit le départ, sur le même chemin — le seul qui voie partir tout le monde, là où le bindQUITne voit que les clients partageant un salon avec le bot. - Le burst ne compte pas comme une arrivée. Il réannonce tout le réseau à chaque relien : sans distinction, chaque reconnexion aurait daté d'aujourd'hui l'arrivée de gens présents depuis des jours.
- 229 sessions restées ouvertes, dont certaines du 25/07 : personne n'écrit la fin quand le bot s'arrête entre-temps. Elles sont closes au démarrage, comme le sont déjà les sockets et les authentifications.
- Cinq
putlogde débogage partaient à chaque déconnexion depuis des semaines — le point M-1 dePROJET.md, ouvert de longue date. Retirés.
Vérifié
Un client connecté, deux salons rejoints, puis départ : une seule ligne, portant son hôte masqué, son adresse et son serveur, ouverte à l'arrivée et fermée au départ. Les 229 fantômes clos au démarrage, contrôlés en base — event_type et motif à l'appui, pour s'assurer que c'était bien cette correction qui avait agi et non autre chose.
Ce qui a failli passer
Le type de fermeture devait être reset, plus parlant. Or event_type est un ENUM qui ne le contient pas : sous STRICT_TRANS_TABLES, l'UPDATE entier aurait échoué — dans un catch, sans un mot — pendant que le journal annonçait le nettoyage et que les 229 sessions restaient ouvertes. C'est le mode de panne que ce projet paie le plus souvent. Le domaine a été vérifié avant d'écrire, la valeur other retenue, le motif porte le sens, et le retour de db_exec est désormais lu.
4.6.4 — 29 juillet 2026
Le panel proposait des commandes sans savoir si le serveur en face les comprend. Il n'avait aucun moyen de le savoir : rien ne disait quel dialecte parle un lien, ni ce que ce dialecte permet. C'est le lot 4 de PROTOCOLES.md.
Et deux colonnes de l'onglet Connexions étaient vides depuis toujours.
Évolutions
- Colonne
protosur les liens ULine (unreal,och,hybrid,p10; défautunreal, qui est ce que parlent tous les liens existants). Elle est lue à la connexion et contrôlée : un dialecte non écrit fait refuser le lien avant l'ouverture de la socket, avec la liste de ce qui est disponible. Sans ce contrôle, l'erreur serait tombée à la première commande — donc après le handshake, avec un lien établi, des pseudo-clients annoncés et un panel affichant « actif ». - Table des capacités : ce que chaque dialecte sait faire, décrit dans la façade et publié en base au démarrage. Le panel la lit ; il ne la recopie pas. Deux tables à tenir d'accord divergent au premier ajout, et c'est toujours celle qu'on affiche qui a tort.
- Page Liens ULine honnête : le dialecte de chaque lien s'affiche et se change ; les dialectes décrits mais pas encore écrits sont proposés et désactivés, ce qui répond à « pourquoi ce choix manque-t-il ? » sans ouvrir le code. Le tableau des capacités donne le motif au survol de chaque
partielet de chaquenon.
Bugs corrigés
- « Hôte masqué » et « Adresse » vides dans l'onglet Connexions. Mesuré : sur 479 sessions, 138 portaient un hôte masqué et une seule une adresse. Ce n'était pas une perte de donnée — le lien reçoit ces champs dans chaque commande
UIDet les écrit fidèlement dansonline. C'estsession_openqui ne les a jamais eus : son unique appelant est le bindJOINd'Eggdrop, qui ne connaît quenick!ident@host, et la colonnevhostne figurait même pas dans sonINSERT. Elle les reprend désormais deonline, et le cycle rattrape les sessions ouvertes plus vite que lui — un client qui rejoint un salon dans la seconde de son arrivée devance le rafraîchissement de 30 s.
Vérifié
89 vérifications hors lien (dont tout refus porte son motif : un grisage muet au panel serait un demi-service). En production : migration appliquée sur les deux liens, 44 capacités publiées, refus d'un dialecte non écrit constaté sur le lien d'essai, et une session neuve portant enfin son hôte masqué et son adresse.
Avertissements
- Les primitives ne routent pas encore par lien. La colonne décide aujourd'hui de l'acceptation du lien, pas du langage employé dessus — faute d'un second dialecte à distinguer. Passer l'identifiant jusqu'aux primitives est le premier geste du lot 5, et rien ici ne prétend le contraire.
- Une session est toujours ouverte par entrée en salon, non par arrivée sur le réseau : un client qui rejoint trois salons où le bot siège produit trois lignes, et l'onglet Connexions les montre toutes. Constaté en mesurant (16 lignes pour
IriXen deux heures) ; non traité ici, parce que c'est un changement de ce que « session » veut dire.
4.6.3 — 29 juillet 2026
Trente formes de protocole écrites en clair sur trente sites d'appel. Ajouter un serveur revenait à les retrouver tous, et à espérer n'en avoir oublié aucun. Elles vivent désormais dans IriX/cmds/irix-proto.tcl, derrière une façade qui choisit le dialecte — un seul est écrit, celui d'UnrealIRCd 6, que parle aussi Overtchat-Hybrid. C'est le lot 3 de PROTOCOLES.md, celui qui était annoncé comme le plus dangereux.
Créations
IriX/cmds/irix-proto.tcl— 33 primitives, une façade, un dialecte. Sourcé avant tout ce qui l'appelle, et ne définissant que des procédures : ni configuration lue ni journal écrit au chargement, comme les autres modules.
Évolutions
- Trois règles, chacune tirée d'un défaut déjà payé ici. La façade ne devine pas : le dialecte est déclaré, jamais détecté. Une primitive absente lève une erreur au lieu de rendre une chaîne vide — la commande qui n'existe pas doit se voir davantage que celle qui existe. Et la façade compose sans écrire : elle rend une liste de lignes, que
::soc::ulines::auto::emettrepasse àraw, seul point d'écriture. ::soc::svc::sendremplacé par::soc::svc::envoyer: le premier prenait une ligne déjà écrite en clair par l'appelant, c'est-à-dire l'endroit exact où le dialecte se répandait.- Une exception assumée : le handshake est composé par la façade mais écrit par
puts. Sa première ligne porte le mot de passe du lien, etrawjournalise tout ce qu'il émet.
Bugs corrigés
- Six commandes de service devenues muettes. Le branchement, fait par script, a écrit
\nlittéral là où il fallait un backslash en fin de ligne. Tcl y voit un argument valant un saut de ligne :emettreen recevait trois au lieu de deux, l'erreur était avalée par lecatchdu dispatcher, etKILL,SAJOIN,SAPART,SVSNICK,CHGHOST,CHGIDENTne partaient plus — sans un mot, ni au journal, ni à l'opérateur. Les G-lines fonctionnaient, leurs deux enveloppes ayant été corrigées à la main : la panne était partielle, donc plus difficile à voir. - Le banc ne pouvait pas le voir : il vérifiait la façade sans jamais appeler les enveloppes qui l'appellent. Neuf d'entre elles y entrent désormais, avec un vrai canal et relecture de la ligne écrite.
tests/irix-link.tclne chargeait pas la nouvelle couche : les appels tombaient sur les talons automatiques du banc, le handshake partait vide, et la campagne échouait sur un « EOF pendant burst » qui ne désignait pas sa cause.
Vérifié
78 vérifications hors lien — chaque ligne comparée octet à octet à celle du code d'avant, chaînes relevées avant extraction. 26 de plus font tourner le vrai code d'IriX contre un vrai serveur : handshake, burst, cinq UID injectés, sonde WHOIS, SVSJOIN, SVSMODE, SVSNICK, CHGHOST. 17 en production, sanctions et commandes de service comprises.
Et la mesure qui tranche : les 45 formes réellement émises depuis le redémarrage ont été confrontées aux 90 formes relevées dans le journal avant l'extraction — aucune nouvelle, aucune modifiée.
Avertissements
- Un banc qui teste la couche du dessous ne dit rien de celle du dessus. C'est ce qui a laissé passer les six commandes muettes, et cela vaut pour tout ce qui sera écrit au-dessus de cette façade.
- Une épreuve lancée trop tôt accuse le code à tort. Après un redémarrage d'IriX, le lien passe
activeavant que les cinq pseudo-clients soient injectés : une campagne lancée dans cet intervalle reçoit401 No such nick/channelet conclut à une panne. Attendre la présence des cinq.
4.6.2 — 29 juillet 2026
Les avis du serveur se choisissent par catégorie. Jusqu'ici, tout opérateur en +s recevait les 45 avis du serveur : chaque arrivée de client, chaque incident de lien, chaque anomalie technique. Sur un réseau qui grossit, l'avis qui compte finit noyé dans celui qui ne concerne personne — et un modérateur qui ne peut ni relancer un lien ni recharger la configuration lisait quand même tout ce qui s'y rapporte.
Le mode +s dit désormais qu'on veut des avis, et un masque dit lesquels : c connexions, l liens serveur, k sanctions, o opérateurs, s protocole et services, e anomalies techniques.
Créations
Servers/overtchat-hybrid/tests/test-snomask.py— dixième campagne du harnais, 17 vérifications contre le vrai binaire. Chaque vérification a son inverse : constater qu'un avis arrive ne prouve rien tant qu'on n'a pas constaté qu'un autre n'arrive pas. Éprouvée en neutralisant le filtre, qui fait bien échouer les deux vérifications décisives.
Évolutions
- Six catégories d'avis, et un masque par connexion — pas par client : un avis serveur ne va qu'aux opérateurs locaux, un masque n'aurait aucun sens de l'autre côté d'un lien.
sendto_realops()exige désormais sa catégorie, et les 45 appels ont été mis à jour. Elle aurait pu être facultative, avec un défaut « tout le monde la voit » : chaque avis ajouté ensuite aurait glissé dans ce défaut sans que personne s'en aperçoive, et le masque se serait vidé de son sens à mesure que le serveur grandit. Ici le compilateur refuse un appel sans catégorie.MODE <pseudo> +s <lettres>pour se régler en jeu : une liste nue remplace le masque,+cajoute une catégorie,-cla retire. L'argument n'est consommé que s'il est un masque valide de bout en bout — sans cette réserve,MODE moi +s +iavalerait le+iau lieu de le poser.- Le numérique
008est enfin émis. Il était déclaré, avec son format, et n'avait jamais servi — unRPL_SNOMASKque rien n'envoyait. snomaskdans le blocoper: le masque par défaut se déclare par compte. C'est la granularité par grade, etTools/opers-sync.shla dérive du grade du panel selon une règle simple — on reçoit ce sur quoi on peut agir.globalreçoit sanctions, connexions et opérateurs ;ircopsanctions et connexions ;moderateurles sanctions seules.- Rien n'est écrit pour
netadminetadmin, délibérément : un bloc sans directive reçoit tout, y compris les catégories qui n'existent pas encore. Leur figer un masque aujourd'hui leur ferait manquer, en silence, le premier avis d'une catégorie ajoutée demain.
Bugs corrigés
Tools/opers-sync.shpouvait bloquer la synchronisation des opérateurs. Une directive inconnue ne produit pas un avertissement mais fait échouer le chargement entier de la configuration. Le dépôt se met à jour d'ungit pull, le binaire demande une recompilation : entre les deux, ce script — qui tourne toutes les 30 secondes — aurait écrit unsnomaskque le serveur en service ne comprend pas, et chaque enregistrement du panel serait resté sans effet, avec un panel continuant d'annoncer des succès. Le script vérifie donc que le binaire porte la directive, se replie sans elle sinon, et le dit. Constaté à l'œuvre avant le déploiement : le repli s'est déclenché et s'est annoncé.
Vérifié
Les dix campagnes du harnais passent, soit 297 vérifications contre le vrai binaire — dont celle qui fait tourner le code Tcl réel d'IriX contre le serveur. Compilation sans un seul avertissement.
En production le 29/07 à 16:33 : binaire remplacé, overtchatd redémarré, lien ULine rétabli de lui-même et cinq pseudo-clients réinjectés.
Puis 8 vérifications sur le serveur en service : le même avis de connexion franchit le masque complet et ne franchit plus « +s k », tandis qu'une G-line posée par IriX atteint bien l'opérateur réglé sur les sanctions — ce qui éprouve du même coup les commandes du chantier précédent. Bannissement d'essai retiré, STATS g vérifié vide ensuite.
Une première version de cette vérification ouvrait trois connexions et concluait à un filtre défaillant. La production limite à trois connexions par adresse, IriX comprise : le client témoin était refusé, et l'avis attendu n'avait jamais eu lieu d'être émis. Le journal du serveur, qui reçoit tout indépendamment des masques, a tranché en une ligne.
La granularité par grade, éprouvée de bout en bout. Un compte ClaudeMod de grade moderateur a été créé au panel pour cela : le grade seul lui donne snomask "k", écrit dans son bloc par Tools/opers-sync.sh et accepté au rechargement. Sept vérifications en production, sans qu'il règle quoi que ce soit : il ne reçoit pas la connexion d'un client, il reçoit la sanction, et il peut ensuite élargir son masque lui-même — le défaut par grade est un point de départ, pas une clôture. Le journal du serveur confirme que le client témoin s'était bien connecté, sans quoi le « il ne le voit pas » n'aurait rien prouvé.
Avertissements
- Ce correctif demande une recompilation ET un redémarrage. Contrairement à IriX, un rechargement à chaud ne remplace pas un binaire, et le redémarrage déconnecte les clients.
- Le masque ne s'applique qu'aux avis, pas aux confirmations. L'auteur d'une commande reçoit toujours la réponse à sa propre action : ce n'est pas un avis serveur et cela ne passe pas par le masque. La première version de la campagne de test s'y est laissé prendre et croyait voir un filtre défaillant.
- Le journal du serveur reçoit tout, quel que soit le masque de qui est connecté : c'est la trace d'exploitation, elle ne dépend pas du public.
4.6.1 — 29 juillet 2026
Quinze commits, et presque tous le même défaut : un programme qui annonce un succès qu'il n'a pas obtenu. Le panel affichait « IriX activé » sans avoir testé le démarrage ; l'installateur déclarait Eggdrop, Anope et overtchatd installés alors qu'aucun des trois ne pouvait démarrer ; le hook TLS mourait avant le serveur en service ; IriX confirmait des sanctions qu'il n'émettait pas ; et le serveur laissait poser un bannissement universel en répondant « Erroneous Nickname » à tout le réseau. La journée n'a pas ajouté de fonctions : elle a rendu vrai ce que le projet affirmait déjà.
Elle a aussi allégé le dépôt de 67 fichiers — trois installateurs concurrents dont un seul démarrait, une copie Tcl que le code contournait déjà, onze fichiers de configuration que plus aucun code ne lisait.
Créations
Markdown/PROTOCOLES.md— cartographie préalable au chantier « un IriX pour quatre serveurs » (UnrealIRCd, ircd-Hybrid, ircu, Overtchat-Hybrid) : 27 formes de protocole recensées avec fichier, ligne et rôle, un point d'insertion unique pour une future couche d'abstraction, et huit lots de travail ordonnés par rapport valeur/risque. Quatre niveaux de certitude, jamais mélangés : ce qui reste à confirmer est regroupé en §8 plutôt que dilué dans le texte.- Panel Anope (
Web/inc/anope.php,Web/pages/anope.php,Config/systemd/anope.service) — état de l'installation, cycle de vie de l'unité et activation des pseudo-clients. Anope n'ayant ni table ni file dans la base, les deux seuls leviers sont sonservices.conf— un blocmodulecommenté ou décommenté, le reste laissé intact, mot de passe du lien compris — et son unité systemd, appelée par sudo avec des commandes écrites en toutes lettres. Anope est optionnel : son absence est le cas nominal, et la page le dit calmement au lieu d'afficher des boutons qui échoueraient. IriX/tests/test-proto.tcl— banc hors lien : chaque primitive de protocole est appelée pour de bon et la ligne produite comparée octet à octet à celle attendue. Ni socket, ni base, ni Eggdrop ; 39 vérifications en une seconde. C'est l'amorce du lot 2 dePROTOCOLES.md.IriX/tests/epreuve-sanctions.py— épreuve sur le réseau en service : 17 vérifications qui provoquent l'effet et l'observent ailleurs. Aucune ne lit une confirmation.
Évolutions
- Les sanctions réseau d'IriX existent pour de bon. Bannissement (
TKL), changement d'identité (CHGHOST,CHGIDENT), sortie forcée (SVSPART), déconnexion et renommage ne vivaient que sur le chemin ULine hérité, que rien n'ouvrait. Neuf primitives les portent désormais sur le lien qui sert (IriX-sql.tcl, section 6bis-C4), avec douze commandes IRCop sur le service de rôlehub:GLINE,ZLINE,UNGLINE,UNZLINE,GLINELIST,ZLINELIST,KILL,SAJOIN,SAPART,CHGHOST,CHGIDENT,SVSNICK. Durées à la grammaire du serveur (30s,2h,7d; sans suffixe, en minutes). Toutes rendent 1 si la ligne est partie, 0 sinon, et aucune ne confirme d'elle-même. - Convention de source arrêtée : une émission est sourcée par l'UID d'un pseudo-client, jamais par un SID de serveur. Les deux passent le contrôle du serveur, mais un SID ne dit pas quel service a agi dans les avis aux opérateurs.
- Le déploiement d'IriX depuis le panel produit enfin une instance qui démarre.
_irix_configurecollecte l'identité en la reprenant deConfig/identity.jsonet deWeb/inc/config.php— le bot doit annoncer le même réseau que le site et écrire dans la base que le site lit. Le mot de passe SQL va dans leIriX.jsonde l'instance en 600, seul fichier qui en a besoin._irix_ecrire_confgénère une vraie configuration Eggdrop, jamais écrasée si elle existe déjà. - L'installateur distribué est désormais le script, plus un binaire.
shcne compile pas : il chiffre le script et le repasse à bash comme argument unique, or le noyau plafonne un argument à 131 072 octets et le script en fait 286 250. Le binaire se produisait sans erreur puis sortait sur « Argument list too long » sans exécuter une ligne. Les cléssetup,setup-devet l'ancien nomovertchat-setuppointent tous le.sh;CORE_WATCHED,git.setupetimportsuivent. Tools/identite-config.shconnaît le serveur en service. Il ne confrontaitConfig/identity.jsonqu'à UnrealIRCd, dont le répertoire n'existe plus, et sautait toute la moitié « serveur » en silence. Il lit maintenantovertchat.conf, dont la syntaxe diffère (blocsme { … }etadmin { … }), et rappelle qu'overtchatd doit recharger sa configuration.- Tout l'affichage du panel Bash passe par des primitives
_ui_*—printf '%-32s'rembourre en octets, donc chaque accent décalait une colonne, et les séquences de couleur comptent dans${#chaîne}sans occuper de colonne. Les entrées du menu sont regroupées par thème sans être renumérotées : regrouper aide qui découvre, renuméroter trahit qui s'en sert déjà.
Suppressions
- Trois installateurs concurrents, dont un seul démarre.
Builder/ne s'exécutait plus depuis des mois pour quatre raisons indépendantes (unsource ../installer/en minuscule qui passe sous Windows et échoue sous Linux, unsourcesur un binaire ELF, un MAKEFILE décrivant une arborescence inexistante, un clone vers un dépôt qui n'est plus le remote).Installer/est supplanté parBootstrap/overtchat-setup.sh: douze de ses quatorze fichiers avaient zéro référence, et sonuninstall.shéchouait à sa quatrième ligne, juste avant unrm -rfsur une variable non définie. Projet/01-04-2026/— aucune référence hors du journal, quatre binaires ELF versionnés dont deux identiques par MD5 à des copies actives.Components/Service/lib/Tcl/Stable/— présentée comme la source distribuée des modules IriX, elle avait quatre modules en moins et quatre fichiers en retard. Le projet le savait déjà :distrib.phpréacheminait délibérément toute demande versIriX/cmds/. Le manifeste réclame désormais les modules là où ils vivent.IriX-std.tclreste seul sousComponents/, faute d'homologue.- Onze fichiers de configuration que plus aucun code ne lisait —
Config/{config-base,paths,folders,schema,defaults}.jsondécrivaient l'installateur supprimé,Runtime/{env,database}.shsourçaient un binaire sorti du suivi git. Deux candidats ont été écartés après vérification, contre l'avis de l'audit : ils étaient référencés par du code vivant. - Le chemin ULine hérité d'IriX — 41 procédures, 1 140 lignes (
IriX-sql.tclpasse de 10 473 à 9 333). Sont restésrequire_ircop(~30 appels, tout le contrôle IRCop du projet),log_cmdet la synchronisationsync::*: ils ne dépendaient pas du socket. Markdown/{DOCKER,README}.md, forks périmés de leurs homonymes à la racine, qu'ils contredisaient.
Bugs corrigés
Serveur.
- Un champ de décalage sur
TKL, et plus personne ne pouvait se connecter.ms_tklne lisait que la disposition compacte du masque, alors qu'UnrealIRCd 6 le scinde en<user> <hôte>. Au burst, Anope réserve ses sept pseudonymes parTKL + Q * NickServ …: le masque était lu enparv[3], soit*— un Q-line sur*était posé et le serveur répondait « Erroneous Nickname » à tout le monde. Les deux dispositions sont maintenant reconnues au nombre d'arguments. (Correctif compilé et mis en service le 29/07 à 14:47 ; un rechargement à chaud ne remplace pas un binaire.) - Un retrait de bannissement qui échouait en silence.
TKL - G * <hôte>faisait chercher le masque*, introuvable ;ban_remove()échouait sans rien dire pendant qu'OperServ annonçait « deleted from the SQLINE list ». Tout bannissement posé par les services devenait permanent. L'échec est désormais annoncé aux opérateurs. - Un masque
*ou*@*est refusé à l'ajout. Rien ne l'arrêtait.
IriX.
TKLinversait user et hôte —TKL + G <hôte> <user>. Invisible tant que le serveur ne lisait queparv[3]: un/gline vilain@mauvais.frbannissait toutmauvais.fret enregistraitvilaincomme auteur. Les deux correctifs devaient être posés ensemble.!ulinestatusannonçait « DÉCONNECTÉ » en permanence, y compris avec des liens parfaitement établis : il lisait les drapeaux du chemin mort. Il interroge désormais<bot>_ulines_sockets.- Un masque trop large n'était refusé nulle part sur le trajet des services :
ban_mask_too_broadne s'applique pas auxTKL. La règle des quatre caractères significatifs est doublée côté IriX.
Installateur et panel.
- Eggdrop s'installait sans ses modules.
make eggdrop || makes'arrêtait au premier succès, etmake eggdropne construit que le binaire. Sansmodules/, IriX échouait à son premierbind msg,msgétant fourni par le moduleserver— longtemps après que l'installation ait été déclarée réussie. Séquence corrigée, et l'absence de modules fait désormais échouer l'installation. - Anope se compilait sans configuration.
conf/services.conf, sans lequel Anope refuse de démarrer, n'était écrit par aucune étape._anope_configurepart du modèle livré par la version compilée plutôt que d'écrire une conf de zéro : les directives changent d'une version majeure à l'autre. - Le panel déployait un IriX qu'aucun programme ne pouvait lancer :
_irix_startexécutaittclsh, alors qu'IriX est un jeu de scripts qu'Eggdrop héberge — 23bind, 246putlog, 25botnick. L'exécution s'arrêtait à la sixième ligne du premier module. irix-operjoin.tclmanquait au manifeste alors queIriX-sql.tclle source et l'appelle à neuf endroits. Une instance déployée depuis le panel recevait seize fichiers sur dix-sept, et Eggdrop s'arrête au premiersourceintrouvable.- Sept confirmations mensongères au panel, toutes bâties sur le même oubli : la valeur de retour n'était pas lue. « IriX activé » sans test du démarrage, deux
killnon vérifiés, « Mise à jour terminée » sur une erreur de syntaxe Tcl, « ✔ Aucune erreur » quandjqéchouait. - La mise à jour écrasait l'identité du réseau : le scan ramassait
Config/identity.json, propre à chaque installation, donc toujours « en attente », puis remplacé par les valeurs amont — nom du réseau, domaine et adresses de courriel disparaissaient avec un « MIS À JOUR » en vert. _server_updatepouvait détruire une installation ET sa sauvegarde :cp -an'était pas testé avant lerm -rfqui suit.- Le retour arrière ne restaurait qu'une fraction des fichiers : l'horodatage de session était calculé par fichier, créant autant de répertoires de session que de secondes écoulées. Il annonçait pourtant « ✔ N fichier(s) restauré(s) » et rendait 0 dans tous les cas.
- « Vérifier SHA-256 » répondait INVALIDE à tous les coups côté serveurs : la comparaison portait sur la chaîne « null ». Chaque appel marquait un serveur sain en défaut.
- L'archive du site s'installait sans contrôle d'intégrité dès que le manifeste était injoignable, et pouvait réécrire
Bootstrap/etConfig/. _panel_logslisait dans$HOME/Service-Overtchat— donc/rootsous sudo. Il annonçait « log absent » sur une installation dont les journaux se remplissaient normalement.--debugn'était reconnu qu'en première position, alors que c'est--root --debugqu'on tape quand l'installation en root échoue ; et une option inconnue n'était qu'un avertissement, si bien qu'une faute de frappe sur--rootlançait une installation sans élévation.- Deux migrations SQL portaient le numéro 010 : l'ordre était décidé par l'alphabet, pas par le numéro, alors que 011 et 012 font
ALTER TABLEsur une table créée par 010.apply.shrefuse désormais une numérotation ambiguë. make -j"$(nproc)"devientmake -jsinprocest absent — soit autant de jobs que de règles. Repli surgetconfpuis 1, borné à 16.
Exploitation.
- Le renouvellement du certificat s'arrêtait avant le serveur en service. Le hook Let's Encrypt déposait le certificat pour UnrealIRCd puis pour Overtchat-Hybrid ; sous
set -euo pipefail, l'échec du premier bloc — le binaire ayant été retiré — faisait sortir le script avant le second, le seul qui compte. La panne ne serait apparue qu'à l'expiration, sous la forme d'un réseau injoignable en TLS. - Le Dockerfile emportait le mot de passe MySQL dans l'image. Il copiait
IriX/runtime/en entier : absent d'un clone frais, où le build cassait, mais présent hors suivi sur une machine de développement, où le secret partait dans l'image. Le.dockerignorelaissait par ailleurs entrerConfig/access.json. - Une installation neuve d'overtchatd ne démarrait pas :
contrib/install-service.shn'installait aucun des quatre fichiers de politique dontovertchat.conf.exampledéclare les chemins, et dontsrc/conf.cfait une erreur fatale. Son contrôle de configuration était de surcroît lancé hors du préfixe, donc ne portait pas sur ce que le service allait charger. - La mise à jour guettait
Bootstrap/installirix, fichier absent du dépôt, de toute installation et du disque : chaque vérification demandait une URL inexistante et le rapport était rouge en permanence, noyant les pannes réelles.
Documentation.
- Douze fichiers de
Markdown/décrivaient un dépôt qui n'existe plus — arborescence d'avant le nettoyage, serveur hors service, procédures qui envoyaient dans le mur (webchat viawebsocket.conf,git cloneproduisant~/Overtchatquand tout code en dur/home/servus/Service-Overtchat).SECURITY.mdprésentait comme ouverte une alerte déjà traitée, en citant des numéros de ligne devenus vides : une alerte permanente et fausse finit par ne plus être lue. - Numéros de version désalignés :
Config/config.jsonetRuntime/version.jsonannonçaient 3.5.2,Config/irix.json4.0.0, quandVERSIONdisait 4.6.0. Et le logo du panel proclamait « V3.X.X », faute de s'afficher avant la lecture deVERSION. LICENSEportait l'ancien compte GitHub (Servus33), quandREADME.mdetConfig/identity.jsondisentSerVuS033.
Erreurs
- Un diagnostic posé le matin, corrigé l'après-midi. Il a été annoncé que les G-lines d'IriX « bannissaient plus large que demandé » à cause de l'inversion user/hôte. C'était faux dans ses conséquences : IriX n'émettait aucune G-line, le code étant sur le chemin mort. Le correctif reste juste, mais il portait sur du code inerte. Le correctif du serveur, lui, était entièrement fondé — c'est Anope qui posait le Q-line sur
*. - Une prémisse trop large. Le retrait du chemin hérité était demandé jusqu'à ce qu'il ne reste « aucune référence à
::soc::uline::». Trois sous-systèmes bien vivants logent dans ce namespace ; les en déloger aurait été un renommage sans rapport avec le code mort, à risque réel. - Un commit dont le message ne dit pas tout.
7a885d4annonce le correctifTKLet livre aussi le panel Anope complet — 1 139 lignes de PHP, une unité systemd et une règle sudoers. Le journal le rattrape ici.
Avertissements
GLINELISTetZLINELISTne montrent que les sanctions posées par les services. Un opérateur qui pose une G-line en direct n'y figure pas. Un panel qui présenterait ces tables comme « les bannissements du réseau » mentirait :STATSreste la seule source complète.- Le seed du panel est un
INSERT IGNORE: sur une base déjà peuplée, les lignesulineconnectetulinedisconnectsubsistent dans<bot>_commandset devront être retirées à la main si le panel les affiche encore. - Le serveur peut refuser après coup — pseudonyme occupé, hôte invalide. Les commandes d'IriX n'annoncent donc jamais plus que « la commande est partie ».
- Le panel Anope ne relit pas les blocs
uline,operet le module de protocole : ils dépendent du serveur IRC retenu et restent à la charge de l'exploitant. La page le dit plutôt que de laisser croire que tout est réglé. IriX-std.tcl(mode Standard, sans SQL) reste distribué sans équivalent maintenu dans l'arborescence active. À éprouver, ou à retirer du choix d'installation.
4.6.0 — 28 juillet 2026
Overtchat-Hybrid sert le réseau. Le serveur écrit pour ce projet a remplacé UnrealIRCd en production le 28/07/2026 à 14h16, après un essai en cohabitation sans coupure et une bascule de quelques secondes. Le démarrage automatique a suivi : overtchatd est enabled, unrealircd est disabled.
Rien ne change pour les utilisateurs : même nom de serveur, mêmes ports, même certificat Let's Encrypt, mêmes pseudonymes enregistrés, mêmes salons.
Évolutions
- WebSocket (
src/websocket.c,src/sha1.c) — le chat du site parle IRC à travers un WebSocket. Le serveur ne connaissait pas ce protocole : basculer sans lui aurait coupé le point d'entrée public du réseau. Poignée de main RFC 6455, cadrage en lecture et en écriture, ping/pong, contrôle d'origine. - Salons interdits (
src/chanprotect.c) — reprend la politique de création sur demande qu'UnrealIRCd tenait pardeny channel. Différence délibérée : les exceptions l'emportent où qu'elles soient écrites, là où UnrealIRCd fait dépendre le résultat de l'ordre des blocs. - Limitation de débit —
FLOOD_MESSAGESetFLOOD_PERIODétaient documentés et lus par personne. Ils s'appliquent enfin. - Onglet Connexions au panel — historique des arrivées et des départs, avec adresse réelle, hôte masqué, durée et motif de fin.
- 280 vérifications sur neuf campagnes, contre le vrai binaire et de vraies sockets.
Bugs corrigés
- Un lien serveur en TLS ne pouvait pas s'établir. Deux défauts distincts, invisibles en clair : la vérification de chaîne interrompait la poignée de main malgré un commentaire affirmant le contraire, et le handshake IRC n'était jamais émis sur un lien sortant chiffré — le pair attendait un
PASSqui ne venait pas. - Aucun anti-flood. 300 messages en rafale arrivaient entiers à leur destinataire, 500
WHOISproduisaient 1000 réponses. Désormais 10 sur 300 passent, soit la limite annoncée. - Le serveur démarrait sans ses fichiers de politique quand il était lancé hors de son préfixe : erreur fatale au démarrage, avertissement au
REHASH. - IriX lisait l'adresse au mauvais rang de la commande
UID— l'index 11 au lieu de 10, c'est-à-dire le premier mot du realname. Une seule des 333 sessions enregistrées portait une adresse plausible. - Le préflight de bascule refusait une configuration qu'il déclarait valide, faute de pouvoir écrire son journal dans
/tmpsous un autre utilisateur. irix.confétait en0644— mot de passe MySQL en clair sur une machine en DMZ.- Un contrôle de la campagne des protections ne testait rien : il s'authentifiait sous un opérateur inexistant.
- Une fenêtre PowerShell clignotait toutes les deux minutes sur le bureau : la tâche de maintien WSL passe désormais par un lanceur
wscriptsans console.
Erreurs
- Trois options de configuration étaient acceptées et lues par personne :
tls-no-verify,flood-exemptetwebsocket. Elles annonçaient des réglages inexistants — le pire des cas, puisqu'on croit avoir décidé quelque chose. La première est retirée, les deux autres sont implémentées. - Le symptôme de lien muet documenté le 27/07 était faux. L'écart entre
LINKSetLUSERSest l'effet normal deHIDE_ULINES. - Le SID recommandé était dangereux :
010appartient à l'espace où IriX puise les SID de ses sockets. Le serveur porte10A.
Avertissements
- Les services ont été muets vingt-cinq minutes après la bascule, entre 14h24 et 14h52. Cause : une reconnexion du lien demandée à chaud pour réinjecter les pseudo-clients. IriX a ouvert un second socket sans fermer le premier, le serveur a refusé (« déjà présent »), et les deux états ont divergé. Seul un redémarrage d'IriX les a remis d'accord. Ne pas relancer un lien ULine à chaud : modifier la configuration des services, puis redémarrer IriX.
- Les mots de passe des blocs
oper{}etlink{}restent en clair : le serveur sait lire une empreinte mais refuse franchement de la vérifier. - IriX régénère le bloc
link{}d'UnrealIRCd depuis la base et ne sait pas écrire celui d'Overtchat-Hybrid : ce bloc se maintient à la main.
4.5 2 versions · 27 juillet 2026 → 28 juillet 2026 4 créations 6 évolutions 7 modifications 4 bugs corrigés 5 erreurs 9 avertissements
4.5.7 — 28 juillet 2026
Version qui lève les blocages de la bascule vers Overtchat-Hybrid. La configuration de production est écrite et éprouvée en cohabitation ; le certificat TLS n'était pas le problème qu'on croyait ; celui qui l'était n'avait pas été vu.
Rien de ceci n'est encore en production. UnrealIRCd continue de servir le réseau,
overtchatd.servicerestedisabled/inactive. Mais plus aucun obstacle identifié ne s'oppose à la bascule.
Créations
Servers/overtchat-hybrid/src/chanprotect.cetinclude/chanprotect.h— salons interdits. Le serveur n'avait aucun équivalent dudeny channeld'UnrealIRCd, qui fait respecter la création de salons sur demande depuis le 22/07. Vérifié en essai avant d'être écrit : un client quelconque créait n'importe quel salon. Basculer en l'état aurait rouvert la création à tout le monde, en silence, pendant que le MOTD et le site renvoyaient toujours vers le formulaire.Servers/overtchat-hybrid/conf/salons-interdits.example— le socle, reprenant la politique en vigueur : six salons ouverts, tout le reste sur demande./home/servus/overtchatd/conf/overtchat.conf— configuration de production, en0600. Hors dépôt, comme il se doit.
Modifications
Tools/ircd-bascule.sh— l'essai en cohabitation ne demande plussudo(il ne touche ni à systemd, ni à un port privilégié), n'écoute plus que sur la boucle locale (la machine est en DMZ,ufwinactive), lance le serveur depuis son préfixe, et pose la configuration d'essai en0600comme celle dont elle reprend les mots de passe.Tools/unrealircd-letsencrypt.sh— le hook de renouvellement sert désormais les deux serveurs. Pendant la bascule ils se relaient ; un hook qui n'en connaîtrait qu'un laisserait l'autre avec un certificat figé au jour de son installation.Servers/overtchat-hybrid/tests/test-protection.py— campagne des salons interdits : ordre des lignes sans effet, exceptions insensibles à la casse, exemption des opérateurs, tenue de la politique après unREHASHsans le fichier, refus de démarrer sans lui.DEPLOIEMENT.md,README.mddu serveur,ROADMAP.md,SECURITY.md,OVERTCHAT-HYBRID.md.
Bugs corrigés
- Le serveur démarrait sans ses fichiers de politique. Un
banned-nicksou unbanned-channelsdéclaré mais introuvable ne produisait qu'un avertissement, au milieu de lignes rassurantes — « configuration chargée » comprise. L'essai du matin a tourné sans aucun de ses 80 pseudonymes interdits, et rien ne permettait de s'en apercevoir. C'est désormais une erreur fatale au démarrage, et toujours un simple avertissement auREHASH, où la liste déjà chargée tient encore. - Un contrôle de la campagne des protections ne testait rien. Celui qui vérifie qu'un fichier de comptes lisible par tous ne désarme pas la protection s'authentifiait sous
oper admin, qui n'existe pas dans la configuration de test : leREHASHqu'il croyait déclencher était refusé, et il passait parce que la liste chargée au démarrage tenait encore. irix.confétait en0644sur l'installation hôte — mot de passe MySQL en clair, machine en DMZ, donc lisible par tout compte local.SECURITY.mddemandait de le vérifier depuis le 21/07 ; c'est fait, et corrigé.
Erreurs
- Le nom du bloc
link{}n'est pas une constante. La documentation affirmait qu'il devait valoirservices.overtchat.fr— c'était le nom d'une ligne d'essai, généralisé à tort. Il doit porter exactement leuline_namede la ligne d'IriX_ulines_socketsutilisée. - Le symptôme de lien muet documenté le 27/07 était faux.
LINKSne montrant qu'un serveur là oùLUSERSen compte deux est l'effet normal deHIDE_ULINES yes, visible aussi quand tout fonctionne. La seule vérification qui prouve quelque chose est d'interroger un service. - Le SID recommandé était dangereux.
010appartient à l'espace0XXoù IriX puise les SID de ses sockets, en n'excluant que000et001: le serveur pouvait se faire attribuer le sien par les services, et voir ses UID rejetés en silence. Le serveur porte10A.
Avertissements
- Le certificat est partagé avec UnrealIRCd. La configuration d'Overtchat-Hybrid pointe le fichier déposé pour UnrealIRCd plutôt que d'en garder une copie — une copie vieillirait sans que rien ne le signale. Le hook étendu doit être recopié dans
/etc/letsencrypt/renewal-hooks/deploy/, ce qui demanderoot. - Les mots de passe de la configuration sont en clair. Le serveur sait lire une empreinte
$algo$…mais refuse franchement de la vérifier : le mécanisme n'est pas implémenté. Régression assumée par rapport à l'argon2 d'UnrealIRCd, le temps que le hachage arrive. IriX.jsonetirix.confexistent aussi dans la copie Windows, sousOneDrive— donc synchronisés chez un tiers, où les droits POSIX ne signifient rien.- IriX régénère le bloc
link{}d'UnrealIRCd depuis la base et ne sait pas écrire celui d'Overtchat-Hybrid : après la bascule, ce bloc se maintiendra à la main.
4.5.6 — 27 juillet 2026
Version de consolidation, centrée sur Overtchat-Hybrid, le serveur IRC écrit pour le réseau. Il passe en test bêta : compilé, éprouvé et essayé en cohabitation avec la production, sans la remplacer. Le numéro saute de 4.0.0 à 4.5.6 pour couvrir d'un seul tenant l'ensemble des textes du projet — journal, charte, documentation Docker et messages du serveur —, jusqu'ici mis à jour séparément et déjà désaccordés entre eux.
Rien de ceci n'est en production. UnrealIRCd continue de servir le réseau. Overtchat-Hybrid reste
disabledetinactive, sur une configuration modèle que le préflight refuse. Ce qui suit décrit un serveur prêt à être essayé, pas un serveur mis en service.
Overtchat-Hybrid en test bêta
- Essai en cohabitation validé. Le serveur a tourné sur des ports séparés (7667 clients, 7900 liens) pendant qu'UnrealIRCd servait la production, sans une seconde de coupure. Deux clients réels s'y sont connectés, ont rejoint un salon, échangé des messages et posé des modes.
- Les services IriX s'y lient. Les cinq pseudo-clients — OverTchat, OverHub, OverNick, OverBot, OverWeb — ont été injectés, et les services répondent aux commandes.
- Écoute restreinte. L'essai n'écoutait que sur l'adresse du réseau local. La machine étant en DMZ et
ufwinactive, un port ouvert sur toutes les interfaces aurait été joignable depuis Internet — un serveur d'essai, avec des mots de passe provisoires et sans TLS.
Créations
| Fichier | Rôle |
|---|---|
Servers/overtchat-hybrid/src/nickprotect.c | Pseudonymes protégés par mot de passe et liste de pseudonymes interdits |
Servers/overtchat-hybrid/include/nickprotect.h | Interface du module, et pourquoi il ne remplace pas les services |
Servers/overtchat-hybrid/conf/charte.example | Charte du réseau, servie par /RULES |
Servers/overtchat-hybrid/conf/pseudos-interdits.example | 80 motifs : insultes, injures haineuses, usurpations d'autorité |
Servers/overtchat-hybrid/conf/pseudos-proteges.example | Format du fichier de comptes et droits exigés |
Servers/overtchat-hybrid/tests/test-commandes.py | Sondes, annuaire, outillage d'opérateur, SVS*, TKL, rechargement |
Servers/overtchat-hybrid/tests/test-protection.py | Pseudonymes interdits et protégés |
Tools/wsl-keepalive-check.ps1 | Contrôle périodique du maintien de la distro WSL |
DOCKER.md | Documentation Docker, jusque-là présente sur la machine sans être versionnée |
Évolutions
- Les pseudonymes composés. Un
/nickn'efface plus le pseudonyme de connexion : il le garde devant, séparé d'une barre.SerVuSqui demandetestdevientSerVuS|test. La variante se remplace au changement suivant plutôt que de s'empiler, et/nick merend le pseudonyme nu. La base n'est pas mémorisée mais dérivée du pseudonyme courant : un champ aurait dû être tenu à jour après unSVSNICK, après un renommage par collision et à l'enregistrement, soit autant d'occasions de diverger de ce qui est réellement porté. - Les pseudonymes interdits. Une liste de politique, relue au
REHASH, distincte des Q-lines qui sont des actes de modération. Les motifs fournis sont volontairement resserrés :*con*attraperait Concombre, Constance et Marconi. - Les pseudonymes protégés. Un pseudonyme déclaré n'est accordé qu'à qui présente son mot de passe à la connexion, par
PASS. Le refus est immédiat pour tous les autres : l'usurpateur ne le porte jamais, pas même une seconde. Empreintes SHA-256 salées, sel tiré degetentropy(), comparaison à temps constant, fichier refusé s'il est lisible au-delà de son propriétaire. QLINEetUNQLINEexistent. Voir ci-dessous : elles n'existaient pas.- Le serveur a une charte, servie par
/RULES, qui répondait « aucun règlement publié ». - La couverture de tests double. 146 vérifications sur quatre campagnes, 243 sur six.
Bugs corrigés
| Symptôme | Cause | Portée |
|---|---|---|
| Une Z-line posée sans durée bannissait le motif au lieu de l'adresse | La durée facultative se lit en tête et était reconnue au seul fait de commencer par un chiffre — or un masque de Z-line est une IP, qui commence toujours par un chiffre. ZLINE 203.0.113.50 :spam posait un bannissement sur spam | Sécurité. L'adresse restait libre et l'opérateur recevait une confirmation qui ressemblait à un succès |
| Les Q-lines n'interdisaient rien | ban_check_nick() était écrite, déclarée, compilée — et appelée nulle part. Aucune commande QLINE n'existait par ailleurs | Sécurité. Le README les annonçait comme fonctionnelles ; un pseudonyme injurieux s'enregistrait sans obstacle |
REHASH journalisait bind : Address already in use à chaque port | Le rechargement créait un second objet listener là où le premier tenait déjà le port | Le bruit n'était que le symptôme : un bloc listen{} supprimé restait servi jusqu'au redémarrage, et la liste enflait d'un doublon par rechargement |
| Le préflight de bascule laissait passer les clés de masquage du dépôt | Il cherchait CHANGEZ en majuscules ; les clés du modèle sont en minuscules | Sécurité. Feu vert donné avec les clés publiques en service : le masquage d'hôtes redevenait réversible par quiconque a lu le dépôt |
make test échouait sur tout clone frais | tests/run-tests.sh était enregistré sans le bit exécutable | Des tests qu'on ne peut pas lancer ne protègent personne |
Un /nick <insulte> aboutissait sous la forme base|<insulte> | Le contrôle ne portait que sur le pseudonyme composé, jamais sur la variante demandée | Trouvé par la campagne, pas par relecture. Même défaut pour /nick <pseudonyme d'un tiers>, qui donnait Autre|SerVuS |
Modifications
Tools/ircd-bascule.sh— préflight insensible à la casse, décompte des valeurs restantes, troncature annoncée plutôt que silencieuse. Deux pièges deset -euo pipefailtraités :grepsort en 1 quand il ne trouve rien, c'est-à-dire dans le cas favorable, etheaden milieu de tuyau provoque un SIGPIPE fatal.docker/dctx.ps1—dwsletdcwvisent le daemon natif parDOCKER_HOSTexplicite. Le contexte SSHwslest abandonné : il aboutissait à/run/docker.sock, donc à Docker Desktop, l'inverse de sa promesse.DOCKER.md— le passage affirmant que l'ordre duPATHsuffit à viser le bon daemon est corrigé : il ne suffit pas, l'intégration WSL de Docker Desktop procède par substitution de socket.
Erreurs
- L'intégration WSL de Docker Desktop a été activée puis abandonnée. Elle occupait
/run/docker.sockdans la distro et masquait le daemon natif où vivent les images du projet. Son réglage n'a pas survécu à un arrêt de Docker Desktop, et elle laissait un socket orphelin après chaque redémarrage de la distro. - La production a été coupée deux fois par des manipulations d'essai. Un
wsl --terminatea tué le processus qui maintient la distro WSL allumée, sans que rien ne le recrée : 45 extinctions en deux heures, chacune coupant IRC, le site et la base. Et unUPDATEsur la tableIriX_admina rechargé tous les liens d'IriX au lieu du seul lien d'essai, déconnectant celui de production pendant une minute. Dans les deux cas le service est revenu, mais l'interruption était réelle et évitable.
Avertissements
- Overtchat-Hybrid n'est pas en service et ne le sera pas sans une vraie configuration : le fichier livré reste le modèle, mots de passe
CHANGEZ-MOIet clés de masquage du dépôt compris. - Le port TLS reste fermé faute de certificat. Le serveur refuse de l'ouvrir en clair plutôt que de fournir un port faussement sûr, et le dit au journal.
- Le nom du lien de services doit être
services.overtchat.fr, et non le.netque propose le modèle. C'est le nom qu'IriX annonce dans ses UID ; le désaccord rend les services présents mais muets, sans message d'erreur d'aucun côté. - SASL,
MLOCK, l'historique de salon, WebSocket, les modules chargeables et la résolution DNS inverse restent non implémentés, et annoncés comme tels. REHASHsur un pseudonyme protégé déjà porté ne déloge pas son porteur : la protection agit à la connexion, pas rétroactivement.
4.0 1 version · 22 juillet 2026 1 création 5 évolutions 7 bugs corrigés 1 erreur 6 avertissements
4.0.0 — 22 juillet 2026
Version majeure, et le numéro n'est pas une coquetterie : deux changements modifient la nature du service. Le panel cesse d'être un écran de supervision pour devenir un outil de modération agissant sur les personnes connectées, et le réseau passe de l'ouverture par défaut à la création de salons sur demande. Un exploitant qui met à jour sans lire ce journal verra ses utilisateurs incapables d'ouvrir un salon.
D'autres chantiers du même jour (identité paramétrable, prise de l'oper par le bot, retour au mode miroir) restent non journalisés : ils précèdent cette reprise du journal et n'ont pas été reconstitués.
Chaîne de version
Bootstrap/overtchat-setup.shlit désormaisVERSIONau lieu de le recopier. Le chemin est déduit de l'emplacement du script, la valeur en dur reste en repli, et le format est validé — un fichier abîmé ne se propage pas dans les JSON.Config/access.jsonetConfig/eggdrop.jsonrestent à1.0, contrairement à ce que demandait la feuille de route : leurmeta.versionest écrit à la création et jamais relu, c'est une version de format. L'aligner aurait annoncé un format v4 inexistant.git.irix.filessuivait sept fichiers Tcl sur onze :IriX-src.conf,irix-reset-ircop.tcl,irix-oper.tcletirix-nickguard.tclen étaient absents alors qu'ils sont sourcés. Une installation neuve posait un bot amputé. Les quatre sont ajoutés, etComponents/Service/lib/Tcl/Stable/— la copie que l'updater distribue — est synchronisée depuisIriX/cmds/, qui reste la seule version active.
Changements de nature du service
- Le réseau n'ouvre plus de salons librement.
Config/unrealircd/salons.confinterdit tout salon hors#!Services!,#!Overtchat!et#Csadmin; un client reçoit « 448 Cannot join channel » avec l'adresse du formulaire. Les opérateurs, passés en classeopersaprès/oper, ne sont pas concernés — c'est ce qui leur permet d'appliquer une décision. Les demandes se font sur?p=salonet se traitent au panel, avec une pastille clignotante visible sur toutes les pages tant qu'une attend. - Le panel modère. L'onglet Connectés liste les présents et permet expulsion, ban, déconnexion et attribution de statut. Le panel ne décide de rien : il dépose une demande, et le bot n'exécute qu'après avoir vérifié que le pseudo du demandeur est connecté sur le lien ET porteur du mode +o. Une session web ne prouve aucun access IRC.
- Le serveur a un MOTD. Il n'en avait aucun (
422 MOTD File is missing).
Créations
| Fichier | Rôle |
|---|---|
IriX/cmds/irix-online.tcl | Reflet en base de la présence vue par le lien S2S |
IriX/cmds/irix-modq.tcl | Exécution des actions de modération, après vérification de l'access |
Web/pages/live.php | Onglet Connectés — présence en direct et actions |
Web/pages/demandes.php | Traitement des demandes d'ouverture de salon |
Web/inc/chanreq.php | Dépôt et décision des demandes de salon |
Web/site/salon.php | Formulaire public de demande |
Web/sql/008-mod-queue.sql, 009-chan-requests.sql | Files de modération et de demandes |
Config/unrealircd/ircd.motd | Message du jour, composé depuis identity.json |
Config/unrealircd/salons.conf | Fermeture de la création libre, avec exceptions |
Web/pages/controle.php | Onglet Contrôle — état voulu des services et des liens, actions groupées |
Web/site/chat.php | Page publique du chat (bêta) |
Web/public/assets/chat.js | Client IRC minimal, sur WebSocket |
Web/inc/ircauth.php | File d'identification IRC déposée depuis le site |
Web/sql/007-web-auth-queue.sql | Table IriX_web_authq |
IriX/cmds/irix-nickguard.tcl | Protection des pseudos + exécution de la file d'identification |
Config/unrealircd/websocket.conf | Listener WebSocket, lié à la boucle locale |
Évolutions
- Bandeau d'état de l'accueil.
site_services_hs()ne renvoyait qu'un booléen : le visiteur savait que « quelque chose » était perturbé, sans savoir si cela le concernait.site_etat_public()publie désormais une projection de quatre services nommés côté usage (irc,services,chat,comptes), rafraîchie toutes les 30 s par?p=etat. Rien d'interne n'en sort : ni PID, ni port, ni nom de brique, ni message d'erreur. - Intention contre impulsion. Le panel ne savait poser que des actions ponctuelles. L'onglet Contrôle écrit l'état voulu (
enabled,auto,auto_connect,log_enabled), et::soc::svc::scan_autole rétablit à chaque cycle. Ces colonnes existaient depuis l'origine du schéma sans que rien ne les lise. - Identification sans commande. Un visiteur peut demander son identification IRC depuis
?p=register. Le site ne vérifie rien : il dépose une empreinte, le bot contrôle la présence sur le lien, tranche, prévient par notice et efface l'empreinte. - Protection des pseudos. Notice puis renommage en
Invite…après 60 s. Éteinte par défaut (drapeaunickguard). - Chat dans le navigateur. Listener WebSocket d'UnrealIRCd sur
127.0.0.1:8000, relayé par Apache enwss://…/ws. Client maison, sans dépendance.
Bugs corrigés
- Le burst du lien serveur était jeté. Les lignes
UIDque le serveur envoie juste après le link arrivent en étatWAIT_EOB;on_readne dispatchait qu'enLINKEDet les ignorait. Le registre des clients ne contenait donc que les personnes arrivées après notre connexion :uid_to_nickrestait muet pour toutes les autres,src_is_adminrefusait leurs commandes bien qu'elles soient opérateurs, et la protection des pseudos ne les voyait pas. Défaut structurel, invisible tant que rien ne dépendait de cette liste. - Le registre des clients n'était jamais purgé en masse.
forget_userne retire qu'un UID à la fois, surQUIT— et unQUITsurvenu pendant que le lien est coupé n'arrive jamais. Les UID d'avant une coupure restaient donc en mémoire indéfiniment : la page Connectés a montré trois UID pour un seul bot. La remise à zéro se fait maintenant dansconnect_socket, avant le burst — la placer après l'aurait effacé. - L'identification ne posait aucun mode sur le client.
IDENTIFYécrivaitauth='oui'dans la base et s'arrêtait là. Or cette colonne n'est visible que de nous : le serveur ne savait rien, le WHOIS ne montrait rien, et les salons réservés aux personnes identifiées (+R) n'avaient aucun critère pour filtrer. Un pseudo identifié restait indiscernable d'un pseudo usurpé. Les deux chemins d'identification — commande et site — posent désormaisSVS2MODE +rd <compte>. authsurvivait à la déconnexion.IriX_users.authrestait àouiindéfiniment, la remise ànonn'ayant lieu qu'au démarrage du bot. Le porteur suivant d'un pseudo était donc considéré comme identifié, et la réinitialisation de mot de passe prenait cette marque pour une preuve de présence alors que plus personne n'était connecté.::soc::nickguard::gonela remet ànonà chaqueQUIT.- Trames WebSocket sans fin de ligne. UnrealIRCd en
type textenvoie chaque ligne sans CRLF final. Le client tamponnait jusqu'au séparateur — réflexe correct sur une socket TCP — et ne traitait donc jamais rien, pas même lePINGinitial que le serveur exige avant d'enregistrer un client. Symptôme muet : connexion ouverte, aucune erreur, écran figé sur « Connexion au réseau… ». - Salons mélangés dans le chat.
set::auto-joinplace tout client dans le salon principal en plus du sien : sans filtrage, la liste des présents mélangeait les deux et les messages de l'un s'affichaient comme venant de l'autre. - Cache d'état jamais écrit.
Web/.run/n'existait pas etfile_put_contentséchouait en silence : chaque visite relançaitps. Le répertoire est créé au besoin.
Erreurs
- La première configuration du listener plaçait le bloc
websocketà côté deoptionsau lieu de dedans, et ne chargeait niwebserverniwebsocket. Le test de configuration l'a arrêtée avant tout rechargement. À noter pour qui rencontrerait le cas : le message d'erreur d'UnrealIRCd (« websocket set, but type unspecified ») propose un exemple qui est lui-même faux.
Avertissements
- Trois fichiers échappent à
Tools/identite-config.shet sont à reprendre à la main en cas de renommage du réseau :websocket.conf(listeallow-origin),salons.conf(message de refus) etircd.motd(adresses du site). - Un salon approuvé n'est pas ouvert pour autant. L'approbation inscrit le salon dans
IriX_channels; il reste à ajouter l'exception danssalons.confpuis à recharger. Le panel le rappelle à chaque approbation, mais rien ne l'automatise. - Aucun courriel n'est envoyé au demandeur, ni à l'acceptation ni au refus.
WEBCHAT_CHANSne propose plus que#!Overtchat!:#accueilet#aidesont devenus injoignables avec la fermeture des salons, et les proposer aurait donné un refus à chaque visiteur.- La modération web exige d'être connecté sur IRC et opérateur. C'est le prix de la preuve d'access : sans elle, voler une session du panel donnerait l'oper.
- Les deux dépôts ont divergé (copie de travail Windows et installation de production) : la propagation s'est faite par copie de fichiers, sans passer par git.
3.5 1 version · 21 juillet 2026 13 créations 3 modifications 1 suppression 3 avertissements
3.5.2 — 21 juillet 2026
Version la plus récente. Trois commits : fa98e88, 2f3fa3a, e9bd821. C'est de très loin la livraison la plus lourde du projet : elle introduit d'un seul bloc le module IriX, le panel web, la documentation et l'infrastructure Docker, puis referme deux problèmes de sécurité.
Sécurité (critique)
- Élévation de privilège fermée dans
::soc::reg::on_pass(IriX/cmds/irix-register.tcl, commite9bd821). Le bindbind msg - passétait nu : n'importe quel utilisateur présent sur IRC pouvait l'appeler et obtenir le grade owner Eggdrop (chattr +nmp) ainsi que l'accès DCC. Aggravant :passwdokn'y rejetait pas un mot de passe faux — il le définissait. La gardefind_owner_on_ircétait bien présente dans le fichier mais n'était appelée nulle part ; elle est désormais branchée sur le chemin d'exécution. Le correctif est appliqué aux deux copies du module, dontComponents/Service/lib/Tcl/Stable/irix-register.tcl, celle qui est distribuée par l'installateur. Config/access.jsonretiré du suivi Git (commit2f3fa3a). Ce fichier contenait les empreintessalt:sha256des comptesadminetcoadmin1du panel. Il était suivi depuis le 22/04/2026 (d7c763b). Il est désormais ignoré via.gitignore, qui a été complété — c'était le dernier secret du projet non couvert.
Créations
- Module IriX (
IriX/) : moteur métier Tcl sur MySQL.IriX-sql.tcl(≈ 9 680 lignes),IriX-table.tcl(schéma, ≈ 800 lignes),IriX-sql-IA.tcl(pipeline de modération, ≈ 1 055 lignes),IriX-sql-fixes.tcl,IriX-binds.tcl,irix-register.tcl,IriX-src.conf,IriX.json.example,irix.conf.example, plus les lanceursIriX/bin/installetIriX/bin/irix. - Panel et site web (
Web/) : - couche interne
Web/inc/—auth.php,db.php,ctl.php,sys.php,site.php,layout.php,mailer.php,contact.php,config.example.php; - pages du panel
Web/pages/—dashboard,admin,membres,salons,moder,ia,irix,ulines,services,serveur,logs,activite,messages,login; - site public
Web/site/—accueil,commandes,installation,charte,reglement,contact; - points d'entrée
Web/public/et feuilles de styleapp.css/site.css; - outillage
Web/start.shetWeb/pma-setup.sh. - Tables des comptes du site :
IriX_web_usersetIriX_web_tokens(Web/sql/001-web-users.sql, appliquées parWeb/sql/apply.sh). Les mots de passe du site sont stockés en bcrypt ; les jetons sont à usage unique et conservés hachés. - Infrastructure Docker :
Dockerfile(multi-étages),docker-compose.yml,.dockerignore,.env.example,docker/entrypoint.sh. docker/dctx.ps1: helper de pilotage des deux daemons Docker de la machine de développement (Docker Desktop etdockerdnatif sous WSL) via un contexte SSH.- Documentation (
Markdown/, 13 fichiers) :ARCHITECTURE.md,INSTALL.md,IRIX.md,WEB.md,DATABASE.md,DOCKER.md,SECURITY.md,BACKUP.md,API.md,MODULES.md,LAUNCH.md,PROJET.md,ROADMAP.md,README.md. Tools/backup-overtchat.sh: script de sauvegarde.Bootstrap/overtchat-setup.sh: assistant d'installation, ≈ 5 100 lignes.
Modifications
VERSION: passage de3.1.7à3.5.2.README.mdracine entièrement réécrit (présentation des composants).Config/config.json,Config/defaults.json,Runtime/version.json,.gitignore.
Suppressions
Config/access.json, retiré du suivi (le fichier reste utilisable localement, il n'est simplement plus versionné).
Audit
- Commandes documentées vs code : les 132 commandes décrites sur le site correspondent toutes à un bind réellement enregistré — aucune commande fantôme. En revanche 13 divergences de grade subsistent entre la documentation et le code, dont :
!newopet!delop: annoncés owner, appliqués admin ;!chanlistet!listpub: annoncés admin, ouverts en réalité à tout utilisateur authentifié ;deauth: aucun contrôle de grade.
Avertissements
- Le pipeline de modération par IA est écrit mais jamais amorcé. Le classifieur est un bayésien local, sans appel à une API externe. Les tables
ia_tokens,auto_motsetia_configsont vides : le moteur est donc aveugle et ne classe rien. Par ailleurs le pontia_actionn'est écrit par aucun code du projet. La fonctionnalité est présente dans le dépôt mais non opérationnelle en l'état. - Les 13 divergences de grade ci-dessus ne sont pas corrigées à ce stade : elles sont constatées, pas résolues.
- Le numéro porté par
VERSION(3.5.2) et celui porté parRuntime/version.json(installed: 1.5.0) ne sont pas alignés ;Config/config.jsonannonce encore0.1.0. Trois compteurs de version coexistent donc dans le projet.
3.4 1 version · 23 avril 2026 20 créations 3 évolutions 1 modification 4 bugs corrigés 4 suppressions 6 erreurs 2 avertissements
3.4.2 — 23 avril 2026
Créations
VERSIONà la racine du dépôt (ca8de12), initialisé à3.4.2. Première tentative de numérotation formelle du projet.Config/access.jsonle 22/04/2026 (d7c763b) — fichier de comptes du panel. Il restera versionné à tort jusqu'au 21/07/2026.
Modifications
- Une quinzaine de retouches successives de
Bootstrap/overtchat-setupentre le 21 et le 22 avril, toutes envoyées via l'interface web GitHub sans message descriptif.
Période du 12 au 17 mai 2026 — bascule vers la bibliothèque Tcl
Pas de changement de numéro de version sur cette période.
Créations
Components/Service/lib/Tcl/Stable/IriX-sql.tcl(94b7fcf) etIriX-std.tcl(d2e3564) : séparation du moteur en une branche SQL et une branche standard.Components/Service/lib/Tcl/Stable/irix-register.tcl(f1e5f3d) : module d'enregistrement du owner — c'est ce fichier qui portera la faille du handlerpasscorrigée en 3.5.2.Components/Service/lib/installirix(09a11ab).Projet/01-04-2026/installirixetProjet/01-04-2026/overtchat-setup(90db60b).
Suppressions
Components/Service/lib/IriX.tcl(111e34e) : le monolithe historique, présent depuis le tout premier commit, est retiré au profit de la bibliothèqueTcl/Stable.Components/Service/lib/TCL/Stable(7e7da3c), créé la veille (aec2c55) avec une casse de répertoire erronée (TCLau lieu deTcl).
Erreurs
- Deux commits datés du 13/05/2026 portent le message « Update print statement from 'Hello' to 'Goodbye' » alors qu'ils ajoutent en réalité les deux fichiers Tcl majeurs de la bibliothèque. Le message ne correspond à rien du contenu.
- La correction de casse
TCL→Tcla nécessité une création puis une suppression le jour même.
Période du 1er avril au 12 mai 2026 — le dossier Projet/01-04-2026
Cette phase représente environ 80 commits, presque tous sans message utile, concentrés sur un répertoire de travail daté.
Créations
Projet/01-04-2026/overtchat.json(dc9d7d8), puisdepot.json,package.json,sock.json(da79314).Projet/01-04-2026/overtchat.sh(1dbeeab),setup(6dcd5e7) etovertchat(2d3ddbf).
Évolutions
- Développement itératif intensif :
overtchat.shetsetupsont modifiés des dizaines de fois entre le 1er avril et le 21 avril. La nature exacte de chaque itération n'est pas reconstituable depuis les messages de commit, tous identiques (« Add files via upload »).
Avertissements
- Ce répertoire
Projet/fonctionne comme un espace de travail parallèle à l'arborescence principale (Bootstrap/,Installer/,Components/). Rien dans l'historique n'indique explicitement lequel des deux faisait autorité à cette période.
Période du 4 mars au 20 mars 2026 — installateur et bootstrap
Créations
Installer/colors.sh(67a0585),Installer/control_package.sh(cf5c3f2),Installer/trap.sh(verrouillage et gestion d'erreurs,e5d9dfb).Builder/exposed.sh(téléchargement et validation JSON,7dbf330).Runtime/database.sh(cab4158) etRuntime/env.sh(72c0f41).- Réorganisation de
Components/: passage des scripts debin/verslib/pourServicecomme pourServeur(start.sh,install.sh,uninstall.sh), et création deComponents/Serveur/conf/server.json. Bootstrap/setup.sh(140ee92).
Suppressions
Components/Service/bin/install.shetbin/uninstall.sh,Components/Serveur/install.sh,uninstall.sh,server.json,start.sh— tous remplacés par leurs équivalents danslib/ouconf/.Installer/setup.sh(40a5ca1), unsetup.shcréé à la racine puis retiré le jour même (daf2ef4/be45fe3),Bootstrap/setup.sh(37ebc5f),Bootstrap/overtchat-setup.sh(47a4571).
Bugs corrigés
- Syntaxe de la commande
jqdanssetup.sh(e1d7c46), puis traitement des chemins de dossiers parjq(4352027). - Gestion du statut d'installation dans
setup.sh(d515846,4e9290f). - Gestion d'erreurs et journalisation du script d'installation (
191eed0). - Refactorisation des fonctions de log et export des variables de couleur (
0070f2e).
Erreurs
- Forte instabilité du point d'entrée : quatre fichiers d'amorçage différents (
setup.shà la racine,Installer/setup.sh,Bootstrap/setup.sh,Bootstrap/overtchat-setup.sh) sont créés puis supprimés en deux semaines avant queBootstrap/overtchat-setupne s'impose.
Période du 5 au 9 janvier 2026 — fondations
Premier commit du dépôt : cb61750, le 05/01/2026.
Créations
- Installateur :
Installer/avecconfig.sh,install.sh,installirix.sh,preflight.sh,repair.sh,state.sh,status.sh,ui.sh,uninstall.sh,update.sh. - Constructeur de paquets :
Builder/avecMAKEFILE,build.sh,builder.json,control_package.sh,encrypt.sh,package.sh. - Composants :
Components/Serveur/(serveur IRC) etComponents/Service/(Eggdrop) aveceggdrop.conf,service.json,IriX.tcletlogo.sh. - Configuration :
Config/avecconfig.json,config-base.json,defaults.json,irix.json,paths.json,schema.json, puisfolders.json(601b6d6). - Exécution :
Runtime/aveccmds_irix.json,state.json,version.json, et un répertoire de logs. LICENSEetREADME.md(d7c8785)..gitignore(d786b1d, 06/01/2026).
Évolutions
Runtime/version.jsonpasse deinstalled: nullà1.0.0le 08/01/2026, puis à1.5.0le 04/03/2026 (477aa7a) — valeur inchangée depuis.- Normalisation de la casse de toute l'arborescence le 07/01/2026 (
9fdcda6,6fe564a) :bootstrap/→Bootstrap/,builder/→Builder/,components/server/→Components/Serveur/,config/→Config/,runtime/→Runtime/.
Erreurs
- La normalisation de casse a mal tourné : entre
6ee941eet004758b(07/01/2026), l'intégralité de l'arborescence a été supprimée puis recréée en cinq commits successifs (« Delete bootstrap directory », « Delete builder directory », « Delete components directory », « Delete config directory », « Delete runtime directory ») avant restauration. Aucun contenu n'a été perdu, mais l'historique de ces fichiers est cassé à cet endroit. - Un fichier
overtchat-setupa été déposé à la racine le 05/01/2026 (9590a02) puis supprimé (f45ff40), doublon de celui présent dansbootstrap/. - Une douzaine de commits du 08 et 09/01/2026 sont intitulés « Merge branch 'main' of github.com:Servus33/Overtchat » alors qu'ils ne sont pas des fusions mais de simples modifications de
Bootstrap/overtchat-setup.
Avertissements
- Aucune activité n'est enregistrée dans le dépôt entre le 09 janvier et le 04 mars 2026 — près de deux mois sans commit. Rien dans l'historique n'explique cette interruption.
Récapitulatif des numéros de version observés — ⚠ ARRÊTÉ AU 31/07/2026
⚠⚠ CE TABLEAU NE DIT PAS QUELLE EST LA VERSION COURANTE, et il l'a fait deux fois. Il s'arrête au 31/07/2026 : les versions postérieures ont chacune leur section ## plus haut, et ce tableau ne les reprend pas.
Sa première ligne a annoncé « version courante » pour 4.6.6 cinq versions trop tard — c'est écrit dans sa propre colonne. Puis pour 4.10.0, alors que le dépôt était en 4.19.15 : quatre-vingt-dix versions de retard, sur une page que lisent les visiteurs. La mention est retirée. La seule source est le fichier VERSION à la racine, et la ligne « Version courante » en tête de ce document.
⚠ Le tableau n'a pas été complété jusqu'à aujourd'hui, et c'est délibéré : quatre-vingt-dix lignes recopiées à la main se périmeraient à la prochaine version. Ce qu'il garde d'utile, ce sont les empreintes de commit des premières versions, que l'historique générique ne permet pas de retrouver autrement.
| Version | Date | Commit | Remarque |
|---|---|---|---|
| 4.10.0 | 31/07/2026 | — | Le projet cesse de supposer qu'il est le seul, et qu'il s'appellera toujours pareil |
| 4.9.3 | 31/07/2026 | 80a88b6 | La commande APPEL pour les clients IRC, et le MOTD généré depuis l'état réel du filtre |
| 4.9.2 | 31/07/2026 | — | Le bouton d'appel dans le chat du site, et la charte qu'il porte |
| 4.9.1 | 31/07/2026 | — | Le guide d'installation, et Postfix qui cesse d'envoyer le courrier interne sur Internet |
| 4.9.0 | 31/07/2026 | 1dd8249 | Le système de mise à jour : la route /update, les sept commandes, l'alerte courriel |
| 4.8.0 | 30/07/2026 | — | Les conversations privées, leur onglet et leur modération |
| 4.7.0 | 30/07/2026 | 380f30a | La réservation de pseudo qui expire, et le chat qui sait la proposer |
| 4.6.13 | 30/07/2026 | 84e39ae | Quatre liens, la page Statut, et ce que trois liens simultanés ont révélé |
| 4.6.12 | 30/07/2026 | 2a997bd | Le lien p10 sert : l'op confirmé par le serveur, la sonde qui aboutit |
| 4.6.11 | 30/07/2026 | 893579f | Le décodeur appartient au dialecte |
| 4.6.10 | 30/07/2026 | — | Encodeur p10, et la notion d'« écrit mais pas utilisable » |
| 4.6.9 | 30/07/2026 | — | |
| 4.6.8 | 30/07/2026 | — | |
| 4.6.7 | 30/07/2026 | — | |
| 4.6.6 | 29/07/2026 | — | Ce tableau s'est arrêté ici jusqu'au 30/07 : il annonçait « version courante » cinq versions après |
| 4.6.5 | 29/07/2026 | — | |
| 4.6.4 | 29/07/2026 | — | |
| 4.6.3 | 29/07/2026 | — | |
| 4.6.2 | 29/07/2026 | — | |
| 4.6.1 | 29/07/2026 | — | |
| 4.6.0 | 28/07/2026 | — | |
| 4.5.7 | 28/07/2026 | — | |
| 4.5.6 | 27/07/2026 | — | |
| 4.0.0 | 22/07/2026 | (non commité) | Première dont le numéro n'est écrit qu'une fois, dans VERSION |
| 3.5.2 | 21/07/2026 | fa98e88 | |
| 3.1.7 | 27/04/2026 | 50c281c | |
| 3.1.5 | 27/04/2026 | 08ed0b8 | |
| 3.1.3 | 27/04/2026 | 7086403 | Régression depuis 3.4.2 |
| 3.4.2 | 23/04/2026 | ca8de12 | Création du fichier VERSION |
Aucun numéro de version n'est enregistré dans le dépôt avant le 23/04/2026 : les périodes de janvier à avril 2026 sont donc décrites ici par leurs dates et non par un numéro.
3.1 1 version · 27 avril 2026 2 modifications 1 erreur
3.1.7 — 27 avril 2026
Modifications
VERSIONmis à jour trois fois dans la même journée :3.1.3(7086403), puis3.1.5(08ed0b8), puis3.1.7(50c281c).Bootstrap/overtchat-setupretouché à plusieurs reprises entre le 24 et le 28 avril (38d0cf0,587ae54,6885a62,394284b,c308db1,5f42a1b,f981f7c,df50b6f).
Erreurs
- Le fichier
VERSIONa été créé le 23/04/2026 avec la valeur3.4.2, puis redescendu à3.1.3quatre jours plus tard. La numérotation a donc reculé. L'historique ne donne aucune explication à cette régression ; les commits concernés sont intitulés simplement « Update VERSION ».
Comment lire ce journal
Version courante : 4.45.0 (fichier VERSION à la racine du dépôt).
⚠ Cette ligne-ci est écrite à la main et elle a menti : elle annonçait
4.16.69le 15/08/2026 pour un dépôt en4.16.74, cinq versions de retard. C'estVERSIONqui fait foi, jamais cette phrase —cat VERSION.⚠⚠ ET ELLE A REMENTI LE 16/08/2026, dans la séance même où l'on écrivait ces lignes : restée à
4.18.5pour un dépôt en4.18.6. Découverte par hasard, en voulant l'éditer. Connaître un travers ne protège pas de le refaire — seul un contrôle le ferait.✅ CE CONTRÔLE EXISTE DEPUIS LE 30/08/2026 :
Tools/tests/test-version-concordance.sh, lancé parTools/bancs.sh. Il compare cette ligne au fichierVERSION, vérifie que la première section de ce document est celle de la version courante, refuse les doublons, et compte ce qu'il a lu pour ne pas passer au vert sur un fichier qu'il n'aurait pas su découper. ⚠ Il écarte les titres cités dans un bloc de code — l'exemple d'étiquetage ci-dessous en contient un, et ungrepnaïf le comptait comme une vraie section.
Depuis la 4.0.0,
VERSIONest la seule source :Bootstrap/overtchat-setup.shle lit au lancement (chemin déduit de son propre emplacement, valeur en dur en repli, format validé), etWeb/inc/config.phple lisait déjà. Il n'y a plus de numéro à recopier à la main.
Comment lire ce document
Chaque section correspond à une version ou à une période de développement, de la plus récente à la plus ancienne. Sous chaque section, les changements sont classés par rubriques :
| Rubrique | Contenu |
|---|---|
| Créations | Fichiers, modules ou tables ajoutés |
| Modifications | Fichiers existants retouchés |
| Suppressions | Fichiers ou répertoires retirés du dépôt |
| Évolutions | Changements structurels ou fonctionnels d'ensemble |
| Bugs corrigés | Défauts identifiés et fermés |
| Erreurs | Fautes de manipulation ou incohérences constatées dans l'historique |
| Avertissements | Points ouverts, risques connus, zones non finalisées |
| Vérifié (ou Éprouvé) | Ce qui a été mesuré, et comment. Compté sous le libellé « constats » |
Les rubriques sans contenu pour une version donnée sont simplement absentes.
Le bilan par branche, et d'où viennent ses nombres
Depuis le 13/08/2026, la page publique ?p=versions ne déroule plus les versions d'un bloc : elle affiche d'abord un bilan par branche, puis le détail de chacune, replié.
⚠ Le nombre de versions n'est plus écrit ici, et c'est la raison même de ce paragraphe. Il annonçait « 89, dont 55 pour la seule branche 4.16 » — pour 145 et 76 au 17/08/2026. Le lecteur voyait donc deux comptes contradictoires sur la même page, celui-ci et celui que le bilan calcule dix lignes plus bas. Le bilan, lui, compte à chaque affichage : c'est lui qui fait foi.
⚠ Ces nombres sont COMPTÉS ici, pas rédigés ailleurs. Ils additionnent les entrées rangées sous les rubriques que journal_rubriques() connaît.
⚠⚠ CE TABLEAU ET journal_rubriques() NE COÏNCIDAIENT PAS — corrigé le 25/08/2026, point 50. L'écart jouait dans les deux sens : « Erreurs » et « Avertissements » figuraient ici sans être comptées par personne — 175 sections écrites par des contributeurs à qui ce paragraphe promettait le contraire — et journal_rubriques() connaissait « Vérifié », absente d'ici.
⚠⚠ Pire : un simple RENOMMAGE avait suffi à casser le comptage en silence. Les versions récentes titrent « Éprouvé » là où les anciennes écrivaient « Vérifié » ; la reconnaissance ne cherchait que le second, et neuf sections ne comptaient plus pour rien. Les deux mots sont désormais reconnus comme un seul.
Les huit rubriques comptent maintenant, et un banc l'exige : test-journal.php rougit si ce tableau et journal_rubriques() cessent de coïncider. Recopier une liste à la main ne suffisait pas — c'est précisément ce qui avait divergé. Une version qui n'emploie pas ces rubriques apparaît « non ventilée » sur la page : elle n'est pas vide, elle n'est pas classée. Écrire ses changements sous les rubriques est donc ce qui les fait compter.
⚠ L'importance se lit du numéro, mécaniquement : une branche (4.16) est un jalon majeur, chacune de ses versions (4.16.54) un correctif mineur. Personne n'a à en juger.
⚠ Marquer une version CRITIQUE
Cela ne se devine pas, et aucune règle sur le texte ne le distinguerait sans se tromper — annoncer « critique » ce qui ne l'est pas apprend à ignorer l'étiquette. Elle se pose donc à la main, sur la ligne qui suit immédiatement le titre :
## 4.16.20 — 8 août 2026
> Critique
Sans cette ligne, une version n'est jamais annoncée critique. Et l'absence d'étiquette ne veut pas dire « anodin » : elle veut dire « personne ne l'a qualifié », ce qui est la vérité.
Note sur la fiabilité de l'historique. Jusqu'au 21/07/2026, une grande majorité des commits porte le message générique « Add files via upload » (envois par l'interface web de GitHub) ou « git add . », sans aucune valeur descriptive. Le contenu de ces commits a donc été reconstitué à partir des fichiers réellement touchés. Lorsqu'une période reste indéchiffrable, c'est indiqué explicitement plutôt que comblé par des suppositions.
⚠ Le nombre de commits n'est plus écrit ici : il annonçait 243 pour cette période, et le dépôt en portait plus de 890 au 17/08/2026. Un lecteur pouvait comprendre que le projet entier tenait en 243 commits. git rev-list --count main le donne exactement.
⚠ Ces nombres comptent ce qui est écrit, pas ce qui a été fait. Une version dont les changements ne sont pas rangés sous les sous-titres habituels apparaît « non ventilée » : elle n’est pas vide, elle n’est pas classée. Déduire sa nature de son texte donnerait un chiffre crédible et faux — c’est précisément ce que ce projet reproche aux affirmations non mesurées.