garde(orphelins) : on juge étroit, on cherche large - #24
Closed
Palenza wants to merge 6 commits into
Closed
Conversation
Poser le périmètre d'un mode était typé `Effect<void, never>`. Deux pannes réelles — settings.json abîmé, disque en lecture seule — tombaient dans un `catch` qui journalisait et rendait `void`. L'appelant faisait donc `appliques += 1` sans condition : il n'avait rien d'autre à regarder. Ce n'est pas un défaut d'affichage. La bannière ambre affirme que les agents ne peuvent NI écrire NI lancer de commande. La croire alors que rien n'a été écrit, c'est lancer un agent en le pensant bridé. Le mode de panne d'un garde, c'est de PROMETTRE une protection absente — pire que pas de garde, parce qu'un garde absent ne rassure personne. La pose rend maintenant son issue (`applique` / `settings-illisible` / `ecriture-refusee`), et SAUTÉ cesse d'être confondu avec ÉCHOUÉ : un compte sans dossier propre est attendu et n'appelle aucun geste ; un compte en échec devait recevoir le périmètre et ne l'a pas reçu. Le décompte et la phrase vivent désormais dans un seul module. Le bug est né de leur dispersion : le serveur comptait dans une route HTTP, le client reformulait dans un composant React — deux moitiés intestables qui ne se rencontraient qu'en production. Preuve par mutation, en deux temps. La première passe a montré que remettre le bug d'origine ne rendait AUCUN test rouge côté serveur : le maillon du milieu était nu. Après regroupement, le même mutant (« un échec compte comme un succès ») fait tomber 4 tests, dont celui qui traverse compter → décrire. Trois portes fermées côté pose : succès, fichier abîmé (le fichier de l'utilisateur reste INTACT — l'écraser pour poser un périmètre serait un remède pire que le mal), disque refusant l'écriture. Gates : vp check ✓ · typecheck ✓ · suite complète 2 799 ✓ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…es de l'utilisateur Poser un mode ÉCRASAIT `permissions.deny` et `permissions.allow` en entier. Et le mode Atelier, qui n'impose aucune restriction, ne produit donc aucune règle — il tombait dans la branche « rien à restreindre » et SUPPRIMAIT le bloc `permissions` complet. Un utilisateur qui avait écrit ses propres refus (`Bash(rm:*)`, `Read(**/.env)`), un `defaultMode`, des `additionalDirectories`, perdait tout en cliquant sur le mode qui promet de ne rien restreindre. Sur le disque, définitivement. Le mode le plus inoffensif du catalogue était le plus destructeur. La règle qui referme ça tient en une phrase : ON NE RETIRE QUE CE QU'ON A POSÉ. Elle s'applique sans marqueur — un marqueur mentirait dès que l'utilisateur édite son fichier — parce que le vocabulaire de nos règles est CLOS : les refus sont toujours des noms nus d'outils (neuf valeurs), les autorisations toujours des `Edit(motif)` tirés d'un périmètre déclaré. Tout le reste lui appartient et se recopie intact. Trois conséquences, chacune tenue par un test : · Atelier fusionne au lieu d'effacer — le bloc survit entier ; · un mode restrictif AJOUTE ses refus aux siens au lieu de les remplacer ; · lever le mode rend le fichier exactement à son état d'origine (aller-retour revue → documentation → libre, comparaison de valeurs). La reconnaissance du mode suit : elle comparait les listes ENTIÈRES, ce qui ne marche plus dès qu'on fusionne. Elle ne regarde plus que NOS entrées — sans quoi l'écran aurait dit « aucun mode » sur un mode bel et bien posé. Zone d'ombre assumée et documentée : une entrée écrite à la main identique à l'une des nôtres nous est indiscernable. Le sens de l'erreur est le bon — retirer une autorisation rend la CLI plus prudente, pas moins. Preuve par mutation, trois mutants, tous tués : la suppression du bloc (2 rouges), l'écrasement des listes (3 rouges), la reconnaissance sans filtrage (1 rouge). Gates : vp check ✓ · typecheck ✓ · suite complète 2 804 ✓ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le scanner d'invisibles était quatorze caractères écrits à la main. Il ratait
le BLOC TAG (U+E0000–U+E007F) — le canal documenté de contrebande : chaque
lettre ASCII y a un jumeau strictement invisible, donc une consigne entière se
cache dans un titre de skill. Et il bannissait U+200D sans nuance, alors que ce
même caractère assemble les emoji.
Mesuré sur les 371 fichiers de skills réellement installés :
bloc Tag 0 occurrence → gratuit à bannir
U+FE0F 26 occurrences dans 21 fichiers SAINS
U+200D 4 occurrences dans 1 fichier SAIN — déjà banni
sélecteur collé à de l'ASCII 0 occurrence → c'est LÀ qu'est l'attaque
run de sélecteurs consécutifs 1 au maximum → une emoji en porte UN
D'où deux familles au lieu d'une. SANS APPEL : aucun usage légitime, la seule
présence est une trouvaille — le bloc Tag y entre, mesuré à zéro. À CONTEXTE :
les assembleurs d'emoji, légitimes collés à un pictogramme et seuls. Bannir
U+FE0F en bloc aurait fait hurler le scanner sur vingt et une skills honnêtes,
et un garde qui crie au loup se fait débrancher — donc ne protège plus rien.
Une subtilité a mordu en écrivant le juge : dans `🏳️🌈`, le voisin de gauche du
jointeur n'est PAS le drapeau, c'est le sélecteur de variation. Juger sur le
voisin immédiat condamnait une emoji parfaitement normale. On remonte au
caractère de BASE, celui qui se voit.
La trouvaille dit maintenant OÙ : un humain ne peut pas chercher à l'œil un
caractère qui, par définition, ne se voit pas.
Preuve sur le parc RÉEL, pas seulement en unitaire :
371 fichiers → 0 signalé (l'ancien en criait un, sur U+200D légitime)
piège bloc Tag → détecté
Gates : vp check ✓ · typecheck ✓ · suite complète 2 812 ✓
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…amais Le champ annonçait, aux deux moteurs : « après ce nombre de minutes sans dictée, le moteur est arrêté pour libérer la mémoire ». Sur le moteur LOCAL c'est faux depuis le premier jour — `TranscriptionService` le construit avec `idleTimeoutOverride: Duration.infinity` (GO fondateur du 29/07 : recharger sept secondes à chaque reprise coûtait le premier clip). Le faucheur n'est donc jamais lancé, et le modèle — ~600 Mo — reste en mémoire pour toujours. La faute d'origine est une GÉNÉRALISATION, et elle est de moi : en câblant ce champ j'ai cité le faucheur du SIDECAR (`TranscriptionService.ts:306`) pour justifier une promesse faite aux deux branches. Un réglage lu par le serveur n'est pas un réglage appliqué partout — il faut regarder la branche qu'on emprunte, pas celle qu'on a sous les yeux. Le comportement ne bouge PAS : garder le modèle chaud est une décision, pas un bug. On cesse seulement de prétendre l'inverse — le texte dit désormais quel moteur ce délai gouverne, et le champ est inerte là où il l'était déjà en silence. Un réglage décoratif fait douter de tous les autres. Reste à trancher par Enzo, hors de ce commit : le moteur local tient ~600 Mo en permanence, y compris quand la dictée n'a pas servi de la journée. Gates : vp check ✓ · typecheck ✓ · suite complète 2 812 ✓ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…cliquet posé `orchestrationRecovery.ts` porte une machine de reprise complète : détection de trou de séquence, replay, backoff, abandon après N essais sans progrès. Quarante assertions la couvrent. Elle est verte à chaque CI. Elle n'est appelée par RIEN depuis que « Rewrite client connection architecture » (pingdotgg#2978) a supprimé `packages/client-runtime/src/ environmentConnection.ts`, son unique consommateur. Tracé commit par commit : le module est né avec un consommateur mobile, l'a vu déménager vers client-runtime en 87d12d7, et l'a perdu en pingdotgg#2978. Il a survécu, ses tests aussi, et plus personne ne l'exécute. C'est pire que du code mort franc. Un lecteur — humain ou agent — ouvre le fichier, voit la logique ET les tests verts, et conclut que le comportement est livré. Il construira sa décision suivante là-dessus. Knip ne le voit pas : pour un détecteur de code mort, un import EST un usage, et le fichier de test en fait un. Ce garde pose l'autre question — « quelqu'un d'AUTRE QUE SON TEST l'importe-t-il ? » Mesure : 91 modules dans ce cas sur les cinq zones scannées. Un CLIQUET, pas un mur : la détection des points d'entrée n'est jamais parfaite (`bin.ts` n'est importé par personne et c'est normal), donc on fige la liste du jour et on refuse qu'elle grandisse. Les faux positifs d'aujourd'hui sont neutralisés une fois pour toutes ; toute nouvelle apparition est un vrai signal. Deux détails qui font la différence entre un garde et un décor : · un plancher de fichiers scannés — sous 500, on refuse de conclure, parce qu'un chemin qui a bougé rendrait « zéro orphelin », vert et mensonger ; · les imports sont RÉSOLUS, jamais cherchés en sous-chaîne : « Suggestion » est contenu dans « SuggestionPanel », et un premier jet à la sous-chaîne trouvait 33 orphelins là où la résolution réelle en trouve 91. Contre-épreuve : un module jetable + son test → le garde sort 1 et le nomme. Retiré ensuite, le cliquet retombe à 91. Ce que ce commit ne fait PAS : supprimer les 91. Une suppression de fichier se confirme (D5), et plusieurs sont des points d'entrée légitimes. Gates : vp check ✓ · typecheck ✓ · suite complète 2 812 ✓ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… à tort retirées Correction du garde livré ce matin même. Il ne cherchait les consommateurs que dans les zones qu'il JUGE : un module appelé uniquement depuis `apps/mobile`, `scripts/` ou un fichier de configuration à la racine ressortait accusé à tort. Trouvé en portant le même garde dans le dépôt jumeau, où le premier jet annonçait 64 orphelins pour 6 réels — `lib/sentry-init.ts` en tête, dont l'appelant vit dans `instrumentation.ts`. Ici l'écart est plus petit (91 → 89) parce que les zones jugées couvraient déjà l'essentiel, mais deux modules étaient accusés sans raison. Ce n'est pas un détail de comptage. Un garde qui accuse à tort se fait débrancher, et un garde débranché ne protège plus rien : c'est l'accusation injuste qui le tue, jamais l'oubli. Le sens de l'erreur d'un détecteur doit pencher du côté qui ne dérange personne. Cliquet abaissé à 89 — il ne monte jamais, il descend quand on répare. vp check ✓ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Owner
Author
|
Remplacée par une branche propre depuis |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Correction du garde fusionné ce matin même (PR #23).
Il ne cherchait les consommateurs que dans les zones qu'il juge. Un module appelé uniquement depuis
apps/mobile,scripts/ou un fichier de configuration à la racine ressortait donc accusé à tort.Trouvé en portant le même garde dans le dépôt jumeau, où le premier jet annonçait 64 orphelins pour 6 réels —
lib/sentry-init.tsen tête, dont l'appelant vit dansinstrumentation.ts. Ici l'écart est plus petit (91 → 89) parce que les zones jugées couvraient déjà l'essentiel, mais deux modules étaient accusés sans raison.Ce n'est pas un détail de comptage. Un garde qui accuse à tort se fait débrancher, et un garde débranché ne protège plus rien. C'est l'accusation injuste qui le tue, jamais l'oubli — le sens de l'erreur d'un détecteur doit pencher du côté qui ne dérange personne.
Cliquet abaissé à 89. Il ne monte jamais ; il descend quand on répare.
vp check✓🤖 Generated with Claude Code