Chantiers classés du plus récent au plus ancien. À l'intérieur d'un chantier, entrées les plus récentes en tête.
Uniquement en ajouter en tête du chantier concerné. Format d'une ligne :
Date | Hash | Résumé.
Décision de l'entretien de cadrage du 2026-08-06 (option journal-format,
PROFIL.md) : convertir les entrées ci-dessous à un format borné à 250
caractères aurait contredit la condition de contenu constant qu'exige l'exception
d'A-4 pour une conversion de format — ce ne sont pas des lignes compressibles sans perte. Ce fichier
reste donc tel quel, en ajout seul comme avant, mais ne reçoit plus de nouvelle entrée. Tout ce qui
se passe à partir du 2026-08-06 est journalisé dans AI-HISTORY.log.
20260804-071239 (2026-08-06)| Date | Hash | Résumé |
|---|---|---|
| 2026-08-06 | 01bf9a6 |
Deux branches governance-sync en attente depuis le 2026-08-04 fusionnées,
puis entretien de cadrage relancé (mot-clé GOUVERNANCE). Les deux branches
partaient du même point (0ccad87) : la seconde (20260804-071239, révision
cumulative) est entrée en conflit avec la première sur GOUVERNANCE-IA.md et
GABARITS.md — résolu en retenant la version de la seconde, vérifiée par empreinte Git
contre le dépôt canonique avant de committer (e0f7591 puis 01bf9a6). |
| 2026-08-06 | 01bf9a6 |
Vingt-deux options de PROFIL.md repassées en revue, deux
nouvelles tranchées. support-test confirmé à 8 après relecture
des cinq campagnes menées sur ce projet. Une note d'attribution devenue fausse
corrigée (le nettoyage des 32 commits Claude-Session était déjà fait le 2026-08-01,
la note disait encore l'inverse). registre-livrés tranché à oui — la
roadmap dépasse 1700 lignes, majoritairement des chantiers clos. journal-format
tranché hors du menu de la charte, après clarification explicite : pas le tableau plat HTML que
l'option décrit, mais un vrai AI-HISTORY.log en texte brut (colonnes
horodatage | CHANTIER | Commit/Hash | Explication, 250 caractères max). Les deux
décisions sont des choix de principe ; leur migration reste à faire, consignée en
roadmap. |
| 2026-08-06 | 01bf9a6 |
Table de correspondance Règle N → A-x, retirée de la charte à la
révision 20260803-200821, recréée localement dans
AI-CONTEXT.html pour les seules références que porte encore ce
journal (Règle 7 Cas B, Règle 1, Règle 3) — décision de l'entretien, la charte ne la portant plus
pour personne. |
configDir(), garde-fou multi-processus, journal saturé (2026-08-06)| Date | Hash | Résumé |
|---|---|---|
| 2026-08-06 | 2c15b1e |
Campagne 5 — le garde-fou validé en conditions réelles, un défaut trouvé et corrigé le
jour même. Menée en deux temps, tous deux autonomes, à la demande explicite de l'utilisateur.
Temps 1, depuis l'hôte (VMware vmrun + open-vm-tools, sans clavier ni
souris réels) : VM Campagne A pilotée en ligne de commande — snapshot restaurée,
Tabby 1.0.235 installé et lancé pour de vrai, plugin construit depuis master. Trois
mesures réelles obtenues sans accès GUI : configDir() confirmé une seconde fois (après
Windows) en respectant --user-data-dir sur Linux ; le verrou single-instance d'Electron
confirmé (relancer tabby sur le même profil ne crée ni second processus ni seconde
fenêtre) ; et une découverte inédite — verrouiller le trousseau avant le premier lancement de
Tabby produit un repli basic_text propre, sans blocage, à distinguer du verrouillage
en cours de session que les campagnes de juillet avaient mesuré. Reproduire ce second cas
exigeait de répondre à une vraie invite d'authentification système — hors de portée du pilotage
distant, d'où le temps 2.
Temps 2, une instance Claude Code lancée par l'utilisateur DANS la VM (prompt préparé par l'instance du temps 1, déposé sur le bureau — .tempfiles/CAMPAGNE-5-PROMPT.md, hors
dépôt), avec accès à la vraie session graphique. Rapport intégral :
~/CAMPAGNE-5-RAPPORT.md sur la VM (non rapatrié dans le dépôt), 38 captures d'écran à
l'appui, chaque constat étiqueté [MESURÉ] ou [DÉDUIT].
Le blocage réel a été reproduit — trousseau verrouillé en cours de session, après négociation du vrai backend gnome_libsecret — avec une vraie invite d'authentification
système GTK, distincte d'une fenêtre Tabby (shot-14-blocked.png). Le discriminant a été
mesuré contre de vrais témoins produits par ce vrai blocage : pid vivant à 4 s / 12 s / 59,7 s →
autorisé ; même pid vivant à 71,8 s → suspendu, au franchissement exact de
STUCK_AGE_MS ; pid tué (SIGKILL) à âge constant (21,5 s / 25,6 s) → suspendu
immédiatement, prouvant que c'est la vivacité seule qui tranche. Le témoin survit au
SIGKILL, comme prévu. Cycle complet parcouru : blocage → gel → mort du processus →
consignation → démarrage suivant non gelé (~6 s) → repli manuel → levée explicite depuis les réglages
→ vérification réelle réussie → retour à la normale.
D4 par l'interface Tabby, code à l'appui, est structurellement impossible sur Linux : verrou single-instance dur dans app.asar
(c.app.requestSingleInstanceLock()||(c.app.quit(),c.app.exit(0))), « New window »
conditionné à "darwin"===process.platform, zone de notification désactivée sur Linux,
palette de commandes muette sur le sujet. Le seul chemin vers newWindow() passe par
l'IPC app:new-window, qui empile une BrowserWindow de plus dans le
même processus principal — un second renderer, pas un second processus — donc
inatteignable sur Linux tel quel.
Un défaut réel trouvé sur du vrai code multi-processus, corrigé le jour même : deux processus OS réels (obtenus via deux profils --user-data-dir distincts, faute de pouvoir
en obtenir deux sur un même profil) partageant un témoin fabriqué pour l'occasion ont montré que le
finally de runGuarded() effaçait le témoin sans vérifier qu'il portait
encore son propre pid/since — un concurrent qui repose son témoin pendant
qu'un autre appel est encore gelé se le faisait effacer par erreur. clearOwnWitness()
corrige : le témoin n'est effacé que s'il correspond exactement à ce qui a été écrit (fenêtre TOCTOU
résiduelle documentée, assumée). Reachability mesurée : nulle sur Linux (les deux garde-fous
ci-dessus s'y opposent déjà), atteignable sur macOS via « New window » (deux renderers, un seul
processus principal, un seul configDir, un témoin partagé) — jamais exercée faute de
machine, seul point du chantier « Deux sujets de conception »
qui reste ouvert. Un cas reproduisant exactement ce défaut a été ajouté à la suite de test isolée de
keychainGuard.ts (hors dépôt, comme les autres scripts de test de ce projet).
Deux constats annexes : un config.yaml réduit à un saut de ligne fait
planter Tabby au démarrage (bug de Tabby, hors périmètre, non corrigé) ; et le bouton de vérification
du panneau (keychainRoundTrip()) est caché dès que le plugin est activé
(settingsTab.component.pug, *ngIf="!suspended && !keychain") — a
compliqué la mesure elle-même, pas un chantier ouvert.
État laissé sur la VM par le temps 2 : non éteinte, non redémarrée, Tabby resté ouvert — restaurée à la snapshot Snapshot 1 par le temps 1 dès le rapport rapatrié.
Aucun commit ni push depuis la VM, conformément à la consigne du prompt. |
| 2026-08-06 | 5f60364 |
Validation, en autonomie, du correctif ci-dessous — sans VM, à la demande
explicite de l'utilisateur (« piloté à 100% par l'agent »).
configDir() : instance Tabby jetable réelle, --user-data-dir
vers un profil isolé (jonction NTFS de dev, --remote-debugging-port=9333), aucune donnée
réelle touchée. better-vault.log est apparu dans le profil jetable et non dans le vrai
%APPDATA%\tabby : preuve directe, en Electron réel, que --user-data-dir est
respecté. Livré.
Garde-fou et journal saturé : modules réels ( keychainGuard.ts, logger.ts) compilés en CommonJS et exécutés hors
Electron — aucune dépendance Electron dans l'un ni l'autre, la seule qu'ils partagent
(tabbyConfig.ts) retombe sur son calcul figé quand @electron/remote est
absent. APPDATA redirigé vers un dossier jetable avant le premier require,
pour ne jamais toucher au vrai témoin ni au vrai journal de l'utilisateur. Neuf cas pour le
garde-fou, onze pour le journal, tous conformes — détail dans
§ Deux sujets de conception et
§ Journal saturé.
Ce que ça ne couvre pas, dit explicitement : le déclencheur réel de D4 (plusieurs fenêtres Tabby contre un vrai trousseau Linux verrouillé) reste hors d'atteinte — aucun outillage de VM sur ce poste ( vmrun/VBoxManage absents), et les campagnes précédentes
montrent que la VM a toujours été pilotée à la main par un opérateur humain dans VMware Workstation.
Le déclencheur réel du journal (rafale authentique de getPassphrase() via
ConfigService sur une session à configuration chiffrée) n'est, lui, pas spécifique à
Linux — reproductible sur Windows sans VM — mais n'a pas été fait faute de session sous la main. La
capture de pile de vaultBridge.service.ts n'a été exercée ni isolément ni en réel,
couplée qu'elle est à l'injecteur Angular. Décision de l'utilisateur : documenter tel quel plutôt que
de pousser plus loin cette session-ci.
Une instance Tabby jetable de test, ouverte pour valider configDir(), n'a pas pu être
fermée par l'agent : Stop-Process a été refusé par le classificateur auto-mode, qui ne
peut pas distinguer une fenêtre de test d'une fenêtre réelle de l'utilisateur — laissée à
l'utilisateur. |
| 2026-08-06 | 5f60364 |
Les deux sujets de conception restés ouverts après la clôture des correctifs c3, plus le
constat du journal saturé, ont un correctif écrit — les trois relevaient de
ROADMAP.html comme chantiers ouverts distincts (§
Deux sujets de conception, § Journal saturé).
tabbyConfig.ts : configDir() résout désormais
app.getPath('userData') via @electron/remote — un accès à une propriété
côté processus principal, pas un pont vers safeStorage, donc hors du périmètre du
garde-fou — avec repli sur l'ancien calcul figé si inatteignable, et mis en cache pour le coût de
l'IPC sur un chemin appelé à chaque lecture de réglages. Respecte désormais --user-data-dir
et tout profil alternatif, là où le calcul figé écrivait toujours dans le profil par défaut.
keychainGuard.ts : guardState() discrimine désormais « un
appel est en vol » de « un appel n'est jamais revenu » par pid vivant
(process.kill(pid, 0)) et ancienneté du témoin (STUCK_AGE_MS, 60 s). Un
témoin récent posé par un processus encore vivant n'est plus lu comme une suspension définitive — ce
qui visait la course mesurée sur D4 (une fenêtre refusée à tort pendant que d'autres se
déverrouillaient normalement dans la même seconde).
logger.ts : les répétitions immédiates d'une même ligne sont désormais
étouffées pour log/warn/crit — la première occurrence part en
clair, les suivantes sont comptées puis résumées après 3 s d'accalmie. Vise le constat du 2026-08-03
(jusqu'à 262 lignes de déverrouillage par jour, Tabby rappelant getPassphrase() deux
fois par sauvegarde de configuration chiffrée).
vaultBridge.service.ts : en mode observation (debug),
resolve() capture la pile d'appel synchrone dès son entrée — donc encore celle de
Tabby, avant tout await — pour nommer l'appelant responsable d'une rafale.
Vérifié par npm run build et lint:i18n, tous deux verts.
Non exercé en conditions réelles : ni multi-fenêtres, ni sur trousseau Linux
verrouillé (garde-fou), ni sur une session à configuration chiffrée (journal) — la VM Ubuntu étant
restée hors d'atteinte au moment d'écrire ces correctifs, comme pour la quatrième campagne
(§ Quatrième campagne). Écrit sur la branche
fix/configdir-guard-log-throttle, pas encore fusionné sur master. |
| Date | Hash | Résumé |
|---|---|---|
| 2026-08-05 | 817b673 |
Les quatre lots sont fusionnés sur master
(9c96ec4, 710412e, 9ba5f6f, 817b673, dans cet
ordre), après validation en conditions réelles des trois correctifs de suivi qui restaient à exercer
— pas sur la VM Ubuntu de la quatrième campagne, restée hors d'atteinte, mais sur l'installation
Windows réelle de l'utilisateur (ARA_DESKTOP), le dépôt de développement étant relié au
dossier de plugins de Tabby par jonction NTFS.
Méthode : Tabby relancé en --debug --remote-debugging-port, instance
vivante de VaultBridgeService retrouvée dans les records de l'injecteur racine Angular
(getPlatform()._modules[0]._r3Injector). D'abord la primitive isolée — appeler
safeStorage.encryptString avec le sel du lot 2 deux fois de suite dans le vrai
processus — pour confirmer que le mécanisme produit des chiffrés distincts. Puis le vrai chemin
applicatif : jeton réel purgé, bridge.original stubé pour répondre un mot de passe
choisi sans jamais simuler de frappe sur la pop-up native ni la déverrouiller pour de vrai, puis
learnFromUser() appelé directement. Résultat conforme au code : mot de passe non
vérifiable par passphraseOpensVault() → rien n'est écrit.
Fausse alerte sur le point 2 (sel du chiffrement), et sa cause : les deux jetons comparés par l'utilisateur commençaient tous deux par djEw, lu comme un signe de
non-hasard. C'est simplement v10 encodé en base64, l'étiquette de version que
Chromium/Electron préfixe à tout blob safeStorage quel que soit son
contenu — au-delà de ce préfixe constant, les deux jetons différaient entièrement. Une deuxième
mesure a par ailleurs montré que safeStorage sur Windows (DPAPI) n'est pas
déterministe nativement, contrairement au repli basic_text mesuré sur Linux à
la campagne 4 : le sel corrige un défaut qui, sur ce poste précis, ne se serait pas manifesté sans
lui de toute façon.
Les cinq points du protocole condensé sont validés : toast rouge fixe sur jeton corrompu, jeton différent à chaque ré-enregistrement, phrase corrigée dans les réglages, ligne WARN tenant sur une seule ligne, formats de date cohérents entre notification et
panneau.
Deux conflits de fusion résolus comme prévu, exactement selon la note laissée dans ce document lors de la préparation : dans vaultBridge.service.ts, la structure du lot 1
(pas de purge sur simple échec de lecture) l'emporte, avec briefError() du lot 3
appliqué par-dessus ; dans store.ts, le mécanisme atomique du lot 2 l'emporte sur
l'ancienne version que le lot 3 avait modifiée en parallèle, briefError() appliqué aux
deux String(e)/String(cause) résiduels de writeDirectly() que
le lot 3 ne connaissait pas. Piège #V28 et
#V29 se retrouvent dans AI-CONTEXT.html,
prochain numéro libre #V30, sans trou. |
| Date | Hash | Résumé |
|---|---|---|
| 2026-08-02 | b5738d6 |
Les quatre lots de correctifs sont écrits, la campagne 4 est préparée mais non menée.
Chaque lot vit sur sa branche avec sa documentation ; seul le lot 4 est validé, parce qu'il ne touche
pas le runtime. Détail dans #correctifs-c3 et
#campagne-4.
Le dispositif d'aveuglement des trois campagnes précédentes est caduc, et c'est mesuré. Retirer .AIRules/, CLAUDE.md et .git du clone ne suffit plus :
le bundle de développement porte les commentaires du code en clair, et ceux des correctifs disent
littéralement ce qu'ils corrigent et à quelle campagne ils répondent. La source map porte l'intégralité
des sources ; trois .d.ts sont révélateurs ; et les commentaires SCSS survivent même à la
minification, le CSS étant embarqué comme chaîne JS. Le code raconte ses propres correctifs —
conséquence directe d'un projet qui commente abondamment, ce qui reste une qualité partout ailleurs.
D'où le passage en boîte noire : l'instance ne reçoit ni sources ni source map, et ne
construit rien.
Le protocole se contredisait sur un point, et c'est une instance auditrice qui l'a relevé. Il imposait le paquet .deb, qui exige les droits d'administration, alors qu'il interdit
par ailleurs à l'opérateur toute saisie non déclarée. L'instance du poste B a pris l'archive portable
— même binaire, SHA-256 relevé, aucun privilège — et a déclaré ses écarts d'elle-même. C'est devenu la
méthode imposée aux trois postes. Une contrainte de protocole qui oblige à en violer une autre
est un défaut du protocole, pas de celui qui l'exécute.
Piège #V29 consigné, compteur porté à #V30 (A-6) :
sans author dans son package.json, Tabby ignore le plugin en
silence — findPlugins() déréférence author.name dans un
try qui avale l'erreur. Découvert en réduisant le package.json d'un paquet
aveuglé, et diagnostiqué par l'instance auditrice, pas par l'outillage. À vérifier avant la publication
npm : un paquet sans ce champ s'installerait et ne se chargerait jamais.
Réserve de méthode : deux erreurs de préparation se ressemblent — imposer un format d'installation sans vérifier ce qu'il exige, et réduire un package.json sans vérifier ce
que Tabby y lit. Dans les deux cas, un raisonnement sur ce que le paquet devrait contenir a
tenu lieu de lecture de ce que le code en fait. |
| Date | Hash | Résumé |
|---|---|---|
| 2026-08-02 | 9a67513 |
Lot 2 — better-vault.json s'écrit dans un temporaire NEUF. Quatre
constats, un seul geste sur writeSettings() : unlink puis création en
wx, et le chmod postérieur disparaît. Le temporaire à nom fixe n'est plus
plantable — wx refuse un chemin déjà occupé, quel qu'il soit — le mode 0600 s'applique à
la création, donc le fichier n'existe jamais en 0666 avec le jeton dedans, et le try
couvre les deux chemins au lieu du seul rename. Un échec total est journalisé en
CRIT puis remonte : l'avaler ferait annoncer par
learnFromUser() un enregistrement qui n'a pas eu lieu.
Le défaut a été mesuré avant d'être corrigé, sur un banc comparant l'ancienne séquence à la nouvelle : temporaire occupé par une entrée qui pointe ailleurs, l'ancienne écrit le jeton dans le fichier visé et fait de better-vault.json un alias de celui-ci, la nouvelle
laisse la victime intacte. Les deux passent le cas nominal et la réécriture.
Deux réserves, écrites pour ne pas être reperdues. Le fsync n'est pas
ajouté — la roadmap range l'atomicité sous coupure hors périmètre, et c'est le commentaire qui
affirmait le contraire qui a été corrigé, pas le code : rename protège d'un lecteur
concurrent et de lui seul. Et le scénario du lien symbolique n'est pas exercé :
Windows n'en crée pas sans privilège, le banc a utilisé un lien matériel, qui reproduit le mécanisme
(écriture à travers) sans être la même primitive. Vérification sous symlink : campagne 4. |
| 2026-08-02 | 7608323 |
Lot 1 — le catch de déchiffrement de serveFromToken() ne purge
plus le jeton. decrypt() échoue de la même façon quand le trousseau refuse de
répondre et quand le jeton est corrompu ; purger sur cette indistinction détruisait un bon jeton dès
qu'une invite d'authentification était annulée. Les deux purges qui subsistent reposent sur une preuve
positive que le jeton est faux — échéance dépassée, échec PBKDF2. Pire cas désormais : un
jeton réellement corrompu coûte un déchiffrement raté par démarrage, et reste révocable par
forgetNow() depuis les réglages.
tokenVerified repasse quand même à false : un échec de lecture ne
dit rien du contenu du jeton, et forcer une revérification PBKDF2 au prochain succès coûte
50–100 ms une fois.
Mesuré sur Windows, dans les deux régimes (configuration chiffrée et non chiffrée) : aucune régression sur le chemin nominal, le coffre s'ouvre toujours automatiquement. Le correctif lui-même reste non exercé, et ne l'est pas ici : sous DPAPI il n'existe aucune invite d'authentification à annuler, donc aucun moyen de faire échouer decrypt() pour
cause de trousseau. Il attend la quatrième campagne — un correctif non exercé n'est pas une
amélioration, c'est un pari, et ce projet a déjà produit deux régressions en corrigeant trois
défauts. |
| 2026-08-02 | d72c669 |
Lot 3 — motifs d'échec lisibles, dates dans la langue de Tabby. Les quatre
constats de lisibilité, plus un piège qu'ils ont fait sortir.
briefError() remplace String(e) dans les motifs : une erreur qui traverse
@electron/remote porte la pile de l'autre processus DANS son message, soit neuf lignes à
l'écran et au journal pour un utilisateur qui venait de refuser une authentification — et une entrée
sur neuf lignes dans un fichier dont la rétention raisonne par ligne. La pile est perdue à dessein :
elle décrit le processus principal d'Electron, pas le chemin de code du plugin.
Une date voyage désormais en millisecondes ( Message.dateParams),
troisième catégorie de paramètre à côté de params et sourceParams, et pour
la même raison que la deuxième existait : une valeur rendue une seule fois ne peut pas servir deux
sorties. Les cinq sites appelaient toLocaleString() sans argument, donc dans la
locale du système — ni celle du journal, figé en anglais, ni celle de l'interface, qui suit Tabby. Le
journal rend par logDate(), l'écran par la locale de Tabby ; logger.ts
emprunte le même format, de sorte qu'une ligne et les dates qu'elle cite s'écrivent pareil.
Après un refus d'authentification, le panneau dit que le redémarrage est le seul remède (#V23) : sans cette ligne, l'utilisateur reclique sur un bouton qui n'atteint plus rien. Et withSafeStorage() vérifie que les méthodes existent, non plus
seulement l'objet — safeStorage est un pont IPC, un objet amputé passait le filtre et
échouait plus loin, à l'endroit où le plugin manipule un secret.
Piège #V28, sorti de ce lot. La roadmap prescrivait TabbyFormatedDatePipe. Il existe, il est réexporté, et il ferait lever —
formatDate exige registerLocaleData(), que Tabby n'appelle
jamais (mesuré dans app.asar). Les dates passent par Intl natif. La
leçon dépasse le cas : le contrôle habituel de ce projet (#V1)
répond sur l'export et ne dit rien des préconditions. Non mesuré et assumé :
formatDate lui-même n'a pas pu être exercé hors contexte Angular ; ce qui est mesuré,
c'est l'absence d'appel.
Reste à traiter au merge : vaultBridge.service.ts déverse encore
String(e) sur la ligne que le lot 1 réécrit de son côté. Elle a été laissée telle quelle
pour ne pas fabriquer un conflit entre deux branches qui doivent rester dissociables. |
| 2026-08-02 | 405b67d |
Lot 4 — lint:i18n vérifie enfin l'enregistrement des tables et les collisions
de msgid. Le seul lot validable sans la VM, et il l'est.
Le script tenait sa liste de langues en dur et n'ouvrait jamais src/i18n/index.ts :
une table écrite, traduite, complète, mais absente de TABLES passait au vert sans jamais
s'afficher. Il lit désormais TABLES, vérifie que chaque fichier y est enregistré, que
chaque code enregistré a son fichier et son import, et que ce code existe dans
LocaleService.allLanguages — un 'fr' au lieu de 'fr-FR'
n'active jamais la locale, donc ne lit jamais la table.
Il surveille aussi les collisions avec les msgid de Tabby
(#V24) : valeur identique, signalée pour mémoire ; valeur
différente, échec — ce plugin changerait le vocabulaire de Tabby pour l'application entière. Les deux
listes sont extraites du bundle compilé de tabby-core, où les .po sont
embarqués en JSON : lecture seule, aucune exécution, aucune dépendance ajoutée.
Les quatre contrôles neufs ont été exercés par injection, pas relus — table non enregistrée, code de locale invalide, valeur divergente, bundle absent — et les codes de sortie suivent. L'état mesuré confirme la campagne : 'Cancel' est bien partagée avec Tabby dans
les trois langues, valeurs identiques.
Le cas « tabby-core non installé » annonce ses contrôles comme NON VÉRIFIÉS
au lieu de se taire : un lint dégradé qui ressemble à un lint complet est pire qu'un lint absent. |
| Date | Hash | Résumé |
|---|---|---|
| 2026-08-01 | cb97067 |
Les correctifs D1 et D2 tiennent, et la prémisse de D1 est prouvée. Mesuré sur
Ubuntu / gnome-keyring / Tabby 1.0.235 : sur trousseau verrouillé, le motif rendu est
roundTripFailed, qui n'est atteignable que si keychainStatus() a d'abord
répondu available: true — l'ancien test se serait donc bien déclaré satisfait. C'est la
première fois qu'une campagne de ce projet établit qu'un défaut corrigé existait, au lieu de
constater qu'un correctif ne casse rien.
Mais dix défauts sont sortis du hors-périmètre, tous revérifiés un par un contre les sources avant d'être consignés. Deux touchent la raison d'être du plugin : annuler une invite d'authentification détruit le mot de passe enregistré (le catch de
déchiffrement ne distingue pas « le trousseau refuse maintenant » de « le jeton est corrompu », et le
contrôle sans annulation montre que le jeton survit), et D4 est confirmé jusqu'en usage
spontané — trois fenêtres déverrouillent, une quatrième se refuse le trousseau en invoquant
une opération démarrée dans la même seconde. Aucun des dix n'est une régression de D1/D2 :
ce sont des zones que les campagnes précédentes n'avaient jamais exercées, ce qui fait tomber la
clause « revue de conception » au sens où elle était écrite — elle se déclenche pour un autre motif.
Pièges #V23 à #V27 consignés, compteur porté à #V28 (A-6). Le plus contre-intuitif est #V23 :
la clé d'OSCrypt est mise en cache pour la vie du processus, si bien que
reverrouiller un trousseau en cours de session ne reverrouille rien — tout scénario de test qui
procède ainsi ne teste pas ce qu'il croit tester, et le fait conditionne l'interprétation de la
campagne entière.
L'aveuglement a été perdu en cours de route, et l'instance l'a signalé elle-même : elle a reçu le document de protocole entier, hypothèses de défaut et grille de dépouillement comprises. Les défauts restent des mesures — un défaut trouvé n'est pas un défaut suggéré — mais les verdicts de conformité valent « tient sous l'angle prévu », un cran en dessous de ce qui était visé. Correction pour la campagne 4 : scinder le protocole en un fichier prompt et un fichier dépouillement. C'est la deuxième fois que l'aveuglement se perd par une omission de forme et non par une décision, d'où une séparation de fichiers plutôt qu'une discipline de lecture. Deux consignes du protocole ont été réfutées par leur propre exécution. La purge des transcripts par l'instance elle-même était fausse sur trois plans indépendants — le glob ne couvrait pas le scratchpad, shred ne garantit rien sur ext4 + NVMe, et le classifieur de
permissions lui a refusé le listage qui aurait permis de vérifier la couverture demandée. Remplacée
par un instantané de VM pris avant l'exécution : ne jamais confier à un acteur la
certification de son propre effacement. Et les phrases de test étaient trop longues, ce qui
est un défaut de méthode et non de confort — retaper une phrase de vingt-cinq caractères des dizaines
de fois décourage la répétition des scénarios, donc appauvrit la mesure, sur un plugin dont l'objet
est justement d'éviter cette saisie. |
| Date | Hash | Résumé |
|---|---|---|
| 2026-08-01 | c189450 |
Les quatre pièges du chantier consignés en
#V19 à #V22,
compteur porté à #V23 dans le même geste (A-6). Aucun des quatre n'avait été
anticipé par la préparation du chantier, pourtant détaillée en roadmap depuis le
2026-07-29 — et deux d'entre eux ne produisent ni erreur ni avertissement. |
| 2026-08-01 | 0f3e33a |
Interface traduite en quatre langues, journal figé en anglais. Les 85 chaînes
sources passent en anglais — contrainte et non style : Tabby cherche ses traductions par la chaîne
source, donc une source française afficherait du français à un utilisateur turc. Le mécanisme est
celui de Tabby : TranslateService et LocaleService sont réexportés par
tabby-core (vérifié dans le bundle compilé, pas seulement dans les typings) et
TranslateModule figure dans les exports du module racine, que
src/index.ts importait déjà — aucune dépendance ajoutée.
Deux décisions de l'utilisateur ont modifié la demande initiale du 2026-07-29 (« les cinq langues les plus utilisées, journal d'audit compris ») : quatre langues au lieu de cinq, la cinquième n'ayant pu être justifiée — la mesure faite ce jour-là, taux de complétion des 24 .po de Tabby, ne discrimine pas : dix-huit locales sont à 100 % ; et le journal reste en
anglais, non traduit, plutôt que de passer à des codes d'événement.
Les deux exigences se contredisaient sur cinq chaînes, affichées et journalisées — le motif d'indisponibilité du trousseau et la phrase du garde-fou. D'où src/messages.ts : un Message transporte sa chaîne source et ses paramètres
sans être rendu ; le journal l'écrit en anglais, l'interface le traduit avec la même chaîne comme
clé. Conséquence en cascade sur le témoin du garde-fou, écrit sur disque et relu au démarrage
suivant : il portait la phrase française de l'opération gelée, il porte désormais un identifiant
(#V22).
npm run lint:i18n ajouté : ce mécanisme ne signale jamais une traduction morte, la
phrase retombe simplement en anglais. Le validateur couvre les deux sens, la cohérence des
paramètres et le piège ICU de l'apostrophe collée à une accolade. Ses deux contrôles ont été
éprouvés sur des cas fautifs, pas seulement sur le cas sain.
Validé en conditions réelles sur Windows 11 le même jour, cinq points : panneau traduit, reste de Tabby intact, changement de langue à chaud, locale non couverte rendue en anglais, journal en anglais. Le deuxième point est celui qui compte — le panneau se serait affiché correctement même si #V19 avait été présent. Jamais exercé sur Linux ni macOS. Vit sur feat/i18n-ui, non fusionnée. |
L'entrée du 2026-07-29 qui acte la campagne indépendante situe le rapport dans
.tempfiles/RAPPORT-TEST-LINUX.md. Il est depuis le 2026-08-01 dans
annexes/RAPPORT-TEST-LINUX.md. La mention
d'origine n'est pas corrigée — le journal est en ajout seul (A-4) — et c'est cet encadré qui la
rattrape.
| Date | Hash | Résumé |
|---|---|---|
| 2026-08-01 | 3fd64c8 |
Le rapport sortait d'un dossier dont la règle est de le supprimer.
.tempfiles/ est déclaré jetable par construction par l'option
tempfiles, et ses fichiers exploités « se suppriment directement, sans demander
confirmation ». Or ce rapport est exploité : ses conclusions sont dans la roadmap et dans
le README public. Une session appliquant la règle l'aurait supprimé — et elle aurait eu
raison. Seul le hasard de son versionnement le protégeait. Il porte 516 lignes de mesures qu'aucun
autre document ne contient : extraction D-Bus de la clé safeStorage, jeton forgé par une
application tierce se déclarant tabby et accepté par le plugin, mot de passe maître
reconstitué hors ligne, permissions partiellement réfutées, écriture non atomique.
Constat annexe : il n'était atteignable par aucun lien — deux mentions textuelles en <code>, jamais un <a>. Donc orphelin au sens d'A-8 avant même
la question du dossier. Il porte maintenant son lien de retour, et la
campagne le cite.
Une modification faite à son contenu, la seule : la phrase de test du coffre jetable, écrite en clair trois fois, devient {{phrase-de-test-jetable}}. Le dépôt est
public et l'option visibilité exclut les mots de passe « sous toute forme, y compris
dans un exemple ». La phrase était jetable et son coffre n'existe plus : le risque était nul, la
règle est catégorique — et c'est au deux-centième commit qu'une règle catégorique sert. |
L'option attribution ayant été tranchée le jour même à « co-auteur seul, sans
Claude-Session », les 32 commits qui portaient déjà un lien de session
ont été réécrits pour l'en retirer, sur décision explicite de l'utilisateur. Les trailers
Co-Authored-By sont conservés.
La réécriture touche les 49 commits de la branche, y compris ceux qui ne portaient pas de lien de session : leur contenu est identique, mais leurs parents ont changé. Les hashes cités par les entrées ci-dessous ne résolvent plus. Ils ne sont pas corrigés — le journal est en ajout seul (A-4) — et c'est la table ci-dessous qui les rattrape. Le seul commit inchangé est le commit initial, qui n'a pas de parent.
Vérifié avant publication : arbre racine identique
(0618d5b), 49 commits avant comme après, git diff vide entre l'ancien et le
nouveau sommet — seuls les messages diffèrent.
| Hash cité | Hash réel | Commit |
|---|---|---|
92be359 | 92be359 | Initial commit — inchangé, sans parent |
9572ae0 | 147ab27 | Implement automatic vault unlock via the OS keychain |
5b2b3a9 | 623f768 | Add settings panel, token expiry and per-machine configuration |
e5b192b | d8c05ff | Polish the settings panel, notify on storage, add CLAUDE.md |
719cd0a | c8ea224 | Add public-facing README in English and French |
70cfc72 | 10ad983 | Turn the log into an audit trail with configurable retention |
c5a2489 | f9fddac | Add open and purge buttons for the audit log |
4b5224a | a8f86b4 | Notify on automatic unlock, align log wording with the UI |
e008d5a | 2f672b4 | Anchor the unlock notification to the active tab |
cf201ca | 4358ca8 | Document the encrypted-config/titlebar mismatch as a pitfall |
a5d23d5 | 2f38b3c | Fix the Linux keychain guard: reachable reason, inert control |
c9f434d | 27677be | Hide the keychain-dependent settings instead of greying them |
75de08a | e0c4411 | Record the independent Linux campaign and its three findings |
f0548f8 | 5eac211 | Guard every safeStorage call behind an on-disk witness |
f086739 | 9b39222 | Keep the keychain off Tabby's startup path entirely |
2dc049c | 8c22cff | Make the 0600 mode an invariant and the write atomic |
4c1fe0d | 804f44b | State the guarantee the code actually delivers |
a1243ba | 5bb5617 | Bring the governance folder up to charter compliance |
76e950f | 0bf059e | Add the two mandatory callouts to the governance index |
0328740 | 96a66b8 | Cadrer le projet et le remettre à niveau sur la charte |
| Date | Hash | Résumé |
|---|---|---|
| 2026-08-01 | n/a (réécriture d'historique, hors commit) |
32 trailers Claude-Session retirés de l'historique public par
git filter-branch --msg-filter, suivi d'un force-push. Ils publiaient une URL
de session claude.ai sur un dépôt public. Sauvegarde préalable : tag local
backup-pre-rewrite-20260801 et bundle Git hors dépôt.
Ce que l'opération coûte : tout clone existant du dépôt diverge et doit être
refait ; un lien externe vers un commit de ce dépôt est cassé. Aucun n'était connu au moment de
l'opération. |
Revue au sens d'A-15, cas B, déclenchée par une révision de la charte touchant le noyau (A-7) : la refonte en noyau / options / entretien de cadrage, propagée par la veille automatisée sur une branche dédiée et restée non fusionnée depuis le 2026-07-31 — le périmètre avait alors été volontairement restreint au projet frère. Cette entrée acte la revue et les écarts constatés.
| Date | Hash | Résumé |
|---|---|---|
| 2026-08-01 | 0328740 |
Cadrage complet du projet et remise à niveau. PROFIL.md créé —
profil de départ complet, vingt-deux options tranchées, aucune ligne
« défaut appliqué, non tranché ». Trois réponses changent une pratique au lieu d'enregistrer
l'existant : branches = branche (le projet travaillait en direct sur
master — cette entrée arrive elle-même par une branche),
dépendances = minimales (aucune dépendance d'exécution dans le
package.json, ngx-toastr external, déchiffrement réimplémenté maison), et
attribution = co-auteur seul, sans Claude-Session — le lien de
session publiait une URL claude.ai sur un dépôt public. Le
REMISE-A-NIVEAU.md de la branche de propagation a été rapatrié dans le chantier
Conformité de la gouvernance puis supprimé, comme A-7 le
prescrit. Références « Règle N » migrées vers les identifiants A-x partout sauf
ici (A-4 — la table de correspondance en fin de charte est là pour ça). Compteur de
numérotation posé en tête d'AI-CONTEXT (prochain libre : #V19).
Validateur html-validate installé, npm run lint:airules. |
| 2026-08-01 | 0328740 |
Trois dérives constatées, dont une déjà corrigée ailleurs sans être vue ici.
AI-CONTEXT.html affirmait « ce projet n'a pas encore de code » et décrivait un dépôt ne
contenant que .AIRules/, LICENSE et README.md — quatre jours
après l'écriture du plugin. C'est exactement l'erreur corrigée dans README.html
par a1243ba le 2026-07-29 : la revue d'alors a corrigé la page où la dérive était
constatée sans vérifier les autres. Ensuite, README.html annonçait Linux « jamais
exercé en vrai, faute de machine » alors que deux campagnes y ont été menées. Enfin un chemin de
machine complet figurait dans l'index d'un dépôt public, ce que la liste de l'option
visibilité range explicitement dehors. |
| 2026-08-01 | 0328740 |
Un statut déclassé à l'audit. Le chantier « Correctifs issus de la campagne
Linux » était marqué ✅ sur une validation du 2026-07-29 au matin ; or
osKeychain.ts, vaultBridge.service.ts et le panneau de réglages ont tous
été touchés le soir même par les correctifs D1 / D2. La mesure ne porte donc plus sur ce que
master contient : repassé en 🚧, ce que la troisième campagne devra lever en rejouant
les mesures plutôt qu'en les relisant. Les quatre autres statuts « Livré » ont été confrontés à
l'état réel du dépôt et tiennent. |
Revue au sens de la charte, Règle 7 Cas B : audit d'un
.AIRules/ déjà au format mais qui a dérivé. Déclenchée depuis le projet frère
tabby-better-sidebar, après un rappel de l'utilisateur sur une règle déjà écrite dans la
charte (Règle 1) et restée lettre morte — les fichiers de .AIRules/ se commitent et se
poussent à chaque mise à jour. Cette entrée acte la revue et les écarts constatés.
| Date | Hash | Résumé |
|---|---|---|
| 2026-07-29 | 76e950f |
Les deux callouts .important obligatoires manquaient à
README.html — détection de dérive, et validation utilisateur avant toute écriture dans
AI-HISTORY/ROADMAP. Défaut relevé après que la mention de
conformité eut été posée au commit précédent : le marqueur promettait donc plus que la page ne
tenait, et aurait étouffé la détection même qu'il existe pour déclencher, une session ultérieure
comparant cette date au lieu de relire la charte. Les deux sont rédigés pour ce projet et non
recopiés : la dérive y est aggravée par le socle non contractuel
(#V2 — Tabby peut invalider un invariant sans le moindre
message), et un test réel y est double par nature, configuration chiffrée et non chiffrée,
un succès dans un seul régime ne prouvant rien. |
| 2026-07-29 | a1243ba |
README.html décrivait encore un projet qui n'existait pas. « Dépôt
Git en place, mais pas encore scaffoldé (pas de package.json, pas de src/) »,
vérification de faisabilité « non franchie », prochaine étape « arbitrage puis, s'il est retenu,
scaffold » — tout cela dépassé depuis le 2026-07-28, l'utilisateur ayant arbitré en faveur du
monkey-patch et le plugin étant livré et validé, ce que le callout en haut de la même page
disait déjà. La section conserve ce qui reste vrai de cette vérification : le socle est un
comportement non contractuel de Tabby, à revérifier à chaque mise à jour de l'application.
Corrigés au passage : les cadences d'écriture, restées sur « AI-HISTORY à chaque commit,
ROADMAP à chaque changement de statut » alors que la charte exige depuis le 2026-07-28
le feu vert explicite de l'utilisateur pour ces deux pages ; l'absence de mention « Conforme à la
charte du … » au pied de page ; et .tempfiles/, non ignoré par Git contrairement à la
Règle 1.
La charte voyage désormais avec le projet : GOUVERNANCE-IA.md est copié
à l'identique dans ce .AIRules/, la racine du workspace n'étant pas un dépôt Git — cette
copie versionnée en est la seule sauvegarde. CLAUDE.md enregistre la décision par dépôt
qu'exige la Règle 3 sur les métadonnées d'attribution IA : admises, ce qui
enregistre la pratique déjà présente dans l'historique public. |
Seconde vérification adversariale, sur VM remise au propre, avec un opérateur humain pour les saisies GUI. Les cinq revendications tiennent à la mesure. Mais la campagne a réfuté la justification du garde-fou, et trouvé deux défauts dans le code écrit le matin même — dont un de sévérité haute qui annulait la garantie centrale du plugin. Résultats complets : Deuxième campagne.
| Date | Hash | Résumé |
|---|---|---|
| 2026-07-29 | (cette série) | D1 — la vérification du panneau ne vérifiait rien, et réarmait le gel. « Lever la
suspension et vérifier » appelait keychainStatus(), qui sur Linux ne lit que le
nom du backend — mesuré comme ne touchant jamais le trousseau. Le bouton annonçait donc « disponible »
sur un trousseau verrouillé, effaçait le témoin, et le démarrage suivant regelait. Corrigé par
keychainRoundTrip() : aller-retour chiffrement/déchiffrement réel sur une valeur jetable,
réservé aux actions explicites de l'utilisateur. |
| 2026-07-29 | (cette série) | D2 — learnFromUser() annonçait un enregistrement sans avoir validé la
phrase. Une saisie erronée était chiffrée, écrite et annoncée « enregistrée » pendant que
Tabby affichait BAD_DECRYPT pour la même phrase.
passphraseOpensVault() existait déjà et servait dans serveFromToken() ;
seule l'écriture s'en dispensait (#V18). |
| 2026-07-29 | (cette série) | Réfutation mesurée de la justification du garde-fou. « L'appel ne revient jamais,
sans afficher de dialogue » est faux sur ses deux termes : le système affiche une invite
d'authentification et l'appel rend la main en 4 à 7 secondes dès qu'on y répond. Corrigé dans
keychainGuard.ts, osKeychain.ts,
#V17 et les deux README. Le dispositif reste nécessaire — un
utilisateur absent bloque indéfiniment — mais c'est cette nuance qui rend praticable la vérification
réelle de D1. |
Le raccourci Linux — ne plus appeler isEncryptionAvailable() quand le backend est
résolu — était juste sur le chemin de resolve(), et il a été écrit en connaissance de
cause, avec la mention explicite qu'il ne corrigeait pas le gel. Personne n'a vu qu'il vidait
du même coup la vérification du panneau de réglages, qui passait par la même fonction. Le
défaut n'est pas dans le raccourci : il est dans le fait qu'une fonction bon marché servait à la fois
de diagnostic interne et de preuve montrée à l'utilisateur.
Règle qui en sort : une vérification qui ne peut pas échouer ne vérifie rien. Quand un test cesse de coûter, se demander ce qu'il mesure encore.
Une instance externe, aveugle au dépôt (.AIRules/, CLAUDE.md et
l'historique Git retirés du clone), a mené une vérification adversariale sur Ubuntu 24.04. Elle a
rapporté trois défauts, dont un de sévérité haute qui contredisait le principe fondateur du plugin.
Les trois correctifs ont été écrits dans la foulée. Ils ne sont pas validés : le
banc qui les a vérifiés tourne sous Windows, et le défaut principal est un gel qui ne s'y reproduit
pas. Détail et périmètre de la seconde campagne :
Deuxième campagne.
| Date | Hash | Résumé |
|---|---|---|
| 2026-07-29 | 4c1fe0d |
README : dire la garantie tenue, pas celle qu'on aurait voulue. La ligne « the plugin never blocks access to your vault » n'a délibérément pas été restaurée — la rétablir telle quelle aurait réintroduit le défaut #3 sous un commit de correction. Remplacée par ce que le code délivre vraiment : désactivé, aucun contact avec le trousseau ; activé, un démarrage figé au plus, auto-réparé au suivant. L'exposition au repos y est dite franchement (extraction D-Bus de la clé, mot de passe reconstitué hors ligne), là où le README ne l'admettait qu'en creux. Piège #V17 ajouté. |
| 2026-07-29 | 2dc049c |
0600 devient un invariant, et l'écriture atomique.
writeFileSync(…, { mode }) n'applique le mode qu'à la création : un fichier préexistant
en 644 n'était jamais re-serré. Temporaire dans le même dossier + chmod +
rename règle les deux — le fichier qui atterrit est toujours neuf. Choix délibéré sur
le chemin d'échec : rename peut échouer en EPERM/EBUSY sous Windows, et y renoncer
perdrait le jeton que l'utilisateur vient de confier, donc repli sur l'écriture directe —
avant la journalisation, pour qu'un journal défaillant ne coûte pas le jeton non
plus. |
| 2026-07-29 | f086739 |
Le trousseau quitte le chemin de démarrage. install() est appelée
depuis le constructeur du NgModule, et avant que enabled ne soit consulté : un
keychainStatus() qui n'y servait qu'à journaliser suffisait à figer Tabby, plugin
désactivé compris. Le panneau de réglages portait le même défaut à un endroit pire — il sondait à
l'initialisation d'un champ, et c'est le seul endroit d'où l'on peut désactiver le plugin : le gel
emportait le moyen d'en sortir. |
| 2026-07-29 | f0548f8 |
Garde-fou : un témoin sur disque avant tout appel à safeStorage.
Aucun délai d'attente n'est possible — @electron/remote fait de l'IPC synchrone, le fil
est arrêté dans l'appel. Le seul recours qui survive au gel est une trace écrite avant, relue
au démarrage suivant. Enveloppé au point de passage et non aux appelants : seul
isEncryptionAvailable() a été mesuré bloquant, que encryptString le soit
aussi est un raisonnement — garder le point de passage rend le dispositif correct dans les deux
cas. |
| 2026-07-29 | 75de08a |
Consignation de la campagne et de ses trois constats, rapport intégral versionné
dans .tempfiles/RAPPORT-TEST-LINUX.md. |
La roadmap prévoyait « rendre keychainStatus() non bloquant ». C'est
impossible, et il a fallu le mesurer pour l'admettre : on ne peut pas expirer, depuis le fil
que l'on bloque, un appel qui bloque ce fil. Ce qui a été livré à la place ne supprime pas le premier
gel — il le rend dernier. Garantie plus faible que celle envisagée, et c'est exactement
celle que le README énonce, ni plus.
Corollaire de méthode, le même que la veille : un plan écrit avant la mesure décrit ce qu'on croit possible, pas ce qui l'est. Voir aussi #V17 sur ce qui reste, dans ce correctif, du raisonné plutôt que du mesuré.
Chantier mené sur la branche fix/linux-keychain-guard, fusionnée dans
master après validation de l'utilisateur. Objectif initial : préparer une VM Ubuntu pour
confier au test une instance externe. La préparation elle-même a suffi à mettre au jour deux défauts
— aucune ligne de ce chantier n'était planifiée.
| Date | Hash | Résumé |
|---|---|---|
| 2026-07-29 | c9f434d |
Masquer les réglages inopérants plutôt que les griser. Le premier correctif les
grisait via [disabled] — ils sont apparus atténués mais restés actionnables
(#V16). Un réglage qui paraît inerte sans l'être invite
précisément à la fausse confiance que ce garde-fou existe pour empêcher. Remplacé par des
*ngIf, qui ne dépendent d'aucun comportement tiers. Restent visibles sans trousseau :
la révocation dès qu'un jeton existe, le mode observation — déplacé sous « Journal », sa vraie
famille — le journal et le nom de machine. Chacun pour une raison que le code étaye, pas par
prudence. |
| 2026-07-29 | a5d23d5 |
Ordre des tests dans keychainStatus(), et contrôles neutralisés.
isEncryptionAvailable() renvoie déjà false quand le backend est
basic_text sur Electron 38.8.6 — le contraire de ce qu'affirmait le commentaire du
code. Le test générique sortait donc en premier et le motif expliquant pourquoi
basic_text n'est pas fiable n'était jamais lu
(#V15). Backend interrogé en premier ; le test de
disponibilité reste derrière pour Windows, macOS et les Electron antérieurs. |
Deux hypothèses de départ étaient fausses, et une seule mesure a suffi à les renverser
chacune. La première : qu'un bureau non reconnu ferait retomber Electron sur
basic_text. Une session i3 a montré gnome_libsecret — le service Secret
reste joignable quel que soit le bureau. La seconde : qu'isEncryptionAvailable() restait
true en mode dégradé, croyance écrite noir sur blanc dans le commentaire du code depuis
sa rédaction.
Aucune des deux n'aurait été détectée par relecture : le code était cohérent avec ce qu'on croyait savoir. C'est l'exécution sur une vraie machine qui les a levées, et il aura fallu descendre jusqu'à la console des DevTools de Tabby pour obtenir la valeur qui tranche.
--password-store=basic, ou l'absence de
service Secret. Aucune préparation particulière n'est nécessaire — ce que l'instance de test peut
découvrir seule.--password-store=basic, acceptation en session GNOME avec trousseau déverrouillé,
panneau des réglages conforme dans les deux cas.Chantier de diagnostic pur : aucune ligne de code du plugin n'a changé. Point de départ, un
symptôme signalé la veille — « la barre tout en haut chevauche l'application » — avec deux suspects
désignés, ce plugin et tabby-better-sidebar. Aucun des deux n'était en cause.
| Date | Hash | Résumé |
|---|---|---|
| 2026-07-29 | cf201ca |
Piège #V14 et issue amont sur le désaccord processus principal / renderer.
Les boutons natifs Windows étaient peints par-dessus la tab-bar, et la fenêtre ne pouvait plus être
déplacée par le haut. Cause : le processus principal lit config.yaml avant tout
déchiffrement, ne trouve donc pas appearance.frame: native et active
titleBarOverlay ; le renderer, lui, tourne après déverrouillage du coffre, lit la vraie
valeur et ne rend aucun window-controls-spacer. Correctif appliqué côté utilisateur :
appearance.frame: thin, seule valeur sur laquelle les deux processus s'accordent quand
la config est chiffrée. Signalé en amont : Eugeny/tabby#11539. |
Le diagnostic est passé par le CDP (piège hérité #8) plutôt que par une bissection en désactivant
les plugins un à un — cinq redémarrages de Tabby au lieu d'un par suspect, et surtout des
mesures plutôt que des impressions. Deux d'entre elles ont tranché seules :
navigator.windowControlsOverlay.getTitlebarAreaRect(), qui a donné la zone exacte
occupée par les boutons natifs (x=1911..2048), et l'énumération des enfants de
.tab-bar, qui a montré le .btn-group droit à x=2002..2048 —
donc dedans — et l'absence pure et simple du spacer.
L'exonération des plugins n'a pas reposé sur une bissection mais sur la géométrie : le bord droit de la tab-bar est collé à la fenêtre, que la sidebar consomme 304 px à gauche ou non. Un argument qui tient sans avoir à désactiver quoi que ce soit — donc sans détruire la jonction NTFS de dev (piège hérité #1).
Vérification faite dans les deux régimes : config déchiffrée, l'overlay disparaît
(visible=false) et app-root perd 22 px au profit du vrai cadre OS ;
config rechiffrée avec frame: thin, l'overlay est toujours actif mais le spacer de
138 px est là et le .btn-group s'arrête à x=1910. C'est la seconde
mesure qui compte : c'est le régime réel de l'utilisateur.
Écueil rencontré au passage, consigné dans #V14 : sur l'application installée,
l'introspection Angular est fermée (el.__ngContext__ est un index numérique, pas le
LView, et window.ng n'existe pas). Lire config.store depuis le CDP est
impossible — il a fallu déchiffrer temporairement config.yaml, ce que l'utilisateur a
fait le temps des tests.
Chantier mené sur la branche feat/audit-log, fusionnée dans master après
validation de l'utilisateur — conformément à la règle « .AIRules/ reflète
main » : un état transitoire vit sur sa branche, doc comprise.
| Date | Hash | Résumé |
|---|---|---|
| 2026-07-28 | e008d5a |
Notification de déverrouillage ancrée à l'onglet actif. Demande initiale : un
toast « en bas à gauche », précisée ensuite en « du terminal actif, pas de Tabby au global ».
ngx-toastr ne sachant positionner que par rapport à la fenêtre, la notification est
devenue un élément inséré à la main dans tab-body.content-tab-active
(src/inlineToast.ts). Difficulté principale : au démarrage, le coffre est
déverrouillé avant qu'aucun onglet n'existe — plutôt que de retomber sur un toast de
fenêtre (ce que la demande visait justement à éviter), le plugin attend le premier onglet actif via
AppService.activeTabChange$ et renonce après 30 s. Limite assumée : c'est l'onglet, pas
le panneau actif d'un split. |
| 2026-07-28 | 4b5224a |
Notification à chaque déverrouillage automatique, et vocabulaire harmonisé. Affichée une seule fois par session : le journal réel montre trois déverrouillages en une seconde au démarrage, une notification par appel en aurait donc empilé trois. Bug d'ergonomie corrigé : l'utilisateur ne trouvait pas l'enregistrement de son mot de passe dans le journal, parce que celui-ci disait « confié au keychain de l'OS » là où l'interface dit « enregistré dans le gestionnaire d'identifiants ». Tous les messages visibles disent désormais « trousseau du système » ; « keychain » ne subsiste que dans les commentaires de code. |
| 2026-07-28 | c5a2489 |
Boutons « Ouvrir » et « Purger » le journal dans le panneau. La purge demande
confirmation (platform.showMessageBox) : le journal est le seul historique des
ouvertures du coffre et l'effacer est irréversible. La purge est elle-même journalisée, un fichier
vide étant autrement indiscernable d'un journal n'ayant jamais rien enregistré. Retrait au passage
de la ligne « journal : … » répétée à chaque session, redondante avec le chemin affiché dans le
panneau. |
| 2026-07-28 | 70cfc72 |
Le journal devient un historique d'audit. Horodatage absolu par ligne (les
délais relatifs au démarrage répondaient à « combien de temps après le lancement », jamais à
« quand »), niveaux INFO/WARN/CRIT, et les événements
manquants : révocation manuelle (seule action délibérée ne laissant aucune trace),
activation/désactivation, changement de politique d'expiration. Rétention configurable
(30 / 90 / 365 j / illimitée, défaut 90), appliquée au démarrage, doublée d'un plafond de taille non
configurable — la purge par date ne tournant qu'au démarrage, elle ne protégerait pas d'un incident
journalisant en boucle en cours de session. Les lignes non horodatées sont conservées : mieux vaut
une ligne périmée qu'un événement effacé par un changement de format. Calcul vérifié hors de Tabby
sur les cas limites (frontière 89/90 j, mode illimité, troncature d'un fichier de 90 000
lignes). |
| Date | Hash | Résumé |
|---|---|---|
| 2026-07-28 | 719cd0a |
Dépôt rendu public : github.com/TooMuhtsh/tabby-better-vault.
Branche feat/auto-unlock fusionnée en fast-forward dans master,
puis poussée. README.md (anglais, affiché par défaut) et README.fr.md
(français) reliés par un lien en tête de chacun : fonctionnalités en cases à cocher, installation
depuis les sources, choix techniques et leurs raisons, sécurité et ses limites, feuille de
route, projet frère, crédits. Deux partis pris assumés : annoncer dès l'introduction que le plugin
patche une partie non documentée de Tabby et peut casser à une mise à jour, et créditer
tabby-vault-keepassxc comme antériorité. |
| 2026-07-28 | 719cd0a |
Historique nettoyé avant le premier push — erreur de ma part. Les trois
premiers commits portaient les trailers Co-Authored-By: Claude et
Claude-Session, que l'utilisateur refuse sur ses dépôts publics. La consigne était
connue mais notée « sur le projet frère », et je l'ai appliquée trop littéralement — la raison
invoquée (dépôt public) valait pourtant à l'identique ici. Corrigé avant toute
publication par git filter-branch --msg-filter sur 92be359..HEAD : aucun
force-push nécessaire, l'historique public est né propre. Vérifié après coup sur
origin/master : zéro trailer. Mémoire de collaboration corrigée pour que la règle
s'applique désormais à tous les dépôts de l'utilisateur. |
Les entrées de ce chantier ont été rédigées avant d'être committées — d'où leur mention « pas encore committé », laissée telle quelle (le journal ne se réécrit pas). Les hachages ci-dessous sont ceux après le nettoyage des trailers, donc ceux qui font foi :
| Hash | Contenu |
|---|---|
9572ae0 | Scaffold, spike, première implémentation fonctionnelle
(osKeychain, tokenStore, vaultCrypto,
tabbyConfig, vaultBridge), décision du stockage en fichier séparé. |
5b2b3a9 | Correctif #V11
(store.ts remplace tokenStore.ts/config.ts), expiration du
jeton, panneau de réglages, réglages par machine. |
e5b192b | Habillage du panneau, onglet renommé « Better Vault » et
repositionné, notification d'enregistrement, mode observation avec aperçu,
CLAUDE.md. |
719cd0a | README publics bilingues. |
| Date | Hash | Résumé |
|---|---|---|
| 2026-07-28 | n/a (pas encore committé) | Notification à l'enregistrement du mot de passe, demandée par l'utilisateur :
un toast indique où le secret est stocké, jusqu'à quand, et qu'il est révocable dans les réglages.
Principe retenu : un plugin qui capte un secret ne doit jamais le faire en silence.
ToastrService injecté dans le pont, délai porté à 12 s, échec de notification sans
conséquence sur le déverrouillage. ngx-toastr ajouté en devDependency
pour ses seuls typages — il reste external et n'entre pas dans le bundle ; il a fallu
ajouter @angular/platform-browser@^15 qu'il réclame en peer, sans quoi npm tentait
d'installer Angular 22 et cassait la résolution. |
| 2026-07-28 | n/a (pas encore committé) | Intention notée, pas ouverte : panneau de réglages unifié « Tabby Better * ».
L'utilisateur souhaite à terme un onglet unique regroupant en sous-onglets tous les plugins
tabby-better-* installés. Explicitement repoussé : tabby-better-sidebar est
en développement actif et n'a pas encore de réglages, il n'y a donc rien à unifier. Consigné en
ROADMAP § Panneau unifié avec ce qui est déjà compatible
(logique et présentation séparées), ce qu'il faudra revoir (title/weight,
HostBinding content-box) et la difficulté centrale non tranchée : lequel des plugins
héberge l'onglet parent, sachant qu'aucun ne peut dépendre de l'autre. |
| 2026-07-28 | n/a (pas encore committé) | Panneau validé par l'utilisateur, puis habillage et repositionnement. Onglet
renommé « Better Vault » (au lieu de « Coffre-fort auto ») pour lever toute
ambiguïté avec le coffre natif de Tabby, et weight = 2 pour le classer après les
onglets natifs. Détail du tri, utile à connaître :
a.weight - b.weight + a.title.localeCompare(b.title), et tous les
onglets natifs laissent weight à 0 — ils sont donc rangés par ordre alphabétique. Un
poids ≥ 2 dépasse à coup sûr le ±1 de localeCompare ; en revanche il place l'onglet
après tous les onglets natifs, l'adjacence stricte avec « Vault » étant impossible sans un
titre commençant lui aussi par « Vault ». Ajout d'un SCSS (champs assombris, bordures marquées,
focus visible, séparateurs de lignes) : styles portés par une classe racine
.better-vault-settings et non par :host, inopérant ici (piège hérité
#14). Couleurs en rgba pour rester lisibles sur les thèmes clairs comme sombres. |
| 2026-07-28 | n/a (pas encore committé) | Panneau de réglages et expiration du jeton. Onglet « Coffre-fort auto » via
SettingsTabProvider (src/settings.ts,
src/components/settingsTab.component.{ts,pug}) : activation, nom de machine, politique
d'expiration, bouton « Oublier maintenant », mode observation. Loaders Pug ajoutés au webpack
(piège hérité #3). Expiration en trois modes, défaut « chaque lundi 3 h » choisi par
l'utilisateur ; calcul vérifié sur les cas limites (créneau du jour déjà passé ou non, passage
d'année, changement d'heure). Les quatre besoins « par machine » exprimés sont couverts sans
mécanique dédiée : better-vault.json étant strictement local, chaque poste a
naturellement ses propres réglages. ToggleComponent vérifié comme bien exporté par
TabbyCoreModule avant de l'utiliser dans le template. |
| 2026-07-28 | n/a (pas encore committé) | ✅ Cas « config chiffrée » validé en conditions réelles — #V4
définitivement levé. L'utilisateur ayant activé encrypted: true, le journal
montre « jeton vérifié » puis « coffre déverrouillé » à +532 ms du démarrage, sans
aucune pop-up — contre ~5 s en config non chiffrée, où l'appel n'arrive qu'à la première demande de
secret. La preuve empirique du bon timing sur le chemin de démarrage est donc faite, là où il n'y
avait jusque-là qu'un raisonnement. Plus aucune inconnue technique sur le projet. |
| 2026-07-28 | n/a (pas encore committé) | Défaut de conception corrigé — les réglages ne pouvaient pas vivre dans
config.yaml. Objection soulevée par l'utilisateur après avoir activé le
chiffrement de configuration : « ça ne peut pas fonctionner puisque le paramètre lui-même est
chiffré ». Exact — vérifié sur sa config, où seules vault,
encrypted et configSync restent en clair. Le plugin lisait
config.store.betterVault, qui aurait toujours renvoyé le défaut
enabled: false dans ce régime : le plugin était donc inopérant en config chiffrée.
Correctif : ConfigProvider supprimé, état complet (activation + jeton) déplacé dans
%APPDATA%\tabby\better-vault.json (src/store.ts, remplace
tokenStore.ts et config.ts). Nouveau piège
#V11. À noter que la vérification du mot de passe
(#V10) continue de fonctionner, la clé vault:
restant nécessairement en clair. L'ancien better-vault-token.bin est supprimé au
démarrage ; une ressaisie du mot de passe est nécessaire. |
| 2026-07-28 | n/a (pas encore committé) | ✅ Validé en conditions réelles par l'utilisateur — le plugin remplit son objectif.
Cycle complet observé dans better-vault.log : session 19:02 « mot de passe confié au
keychain de l'OS » après ~25 s de saisie manuelle (apprentissage), puis session 19:03 « jeton
vérifié » et « coffre déverrouillé depuis le keychain de l'OS » — sans aucune pop-up.
Jeton de 47 octets écrit dans better-vault-token.bin, chiffré par DPAPI. La chaîne
complète fonctionne : patch de getPassphrase, chiffrement OS, vérification PBKDF2 locale
contre le coffre de config.yaml, service du mot de passe. Activation faite à la main via
betterVault.enabled: true (pas encore de panneau de réglages). Contexte de validation :
Windows 11, config non chiffrée — le cas encrypted: true reste non
observé. |
| 2026-07-28 | n/a (pas encore committé) | Première implémentation fonctionnelle, compile. Le spike est retiré au profit du
vrai service. Fichiers : osKeychain.ts (safeStorage + refus du backend
basic_text), tokenStore.ts (fichier séparé
better-vault-token.bin), vaultCrypto.ts (vérification locale du mot de
passe), tabbyConfig.ts (lecture du coffre dans config.yaml),
config.ts (ConfigProvider, opt-in par défaut à false),
vaultBridge.service.ts (orchestration). Fonctionnement : jeton présent → vérifié puis
servi ; absent → délégation à la pop-up native puis mémorisation ; périmé ou keychain indisponible →
purge et repli silencieux. Pas encore de panneau de réglages : l'activation passe
par betterVault.enabled: true dans config.yaml. |
| 2026-07-28 | n/a (pas encore committé) | Décision de stockage : fichier séparé, actée après une objection pertinente de
l'utilisateur (« quel risque, puisque le jeton n'est utilisable que sur une seule machine ? »).
L'analyse lui a donné raison sur la confidentialité — le motif retenu est fonctionnel : la
synchronisation de config de Tabby provoquerait une boucle de destruction mutuelle du jeton entre
machines. Voir ROADMAP.html § Stockage. Ajout au passage du
garde-fou Linux basic_text (§ Linux), seul cas où le
jeton exporté serait réellement lisible. |
| 2026-07-28 | n/a (pas encore committé) | Deux corrections de documentation imposées par l'implémentation :
#V6 affirmait que le cache natif n'est
« jamais » rempli — c'est faux dès qu'on délègue à la méthode native (apprentissage, repli), le
piège est donc reformulé en conditionnel ; et l'invariant de purge RAM de la ROADMAP, qui parlait
d'« écraser la variable après injection », a été reformulé à portée constante puisqu'il n'y a plus
d'étape d'injection et qu'une chaîne JS ne peut pas être écrasée. Nouveau piège
#V10 : les paramètres cryptographiques de Tabby sont
répliqués dans vaultCrypto.ts et devront être revérifiés à chaque mise à jour. |
| Date | Hash | Résumé |
|---|---|---|
| 2026-07-28 | n/a (pas encore committé) | ✅ Verdict du spike : le projet est techniquement viable. Six sessions Tabby
journalisées dans %APPDATA%\tabby\better-vault-spike.log.
#V7 entièrement levé —
require("@electron/remote") OK, remote.safeStorage présent,
isEncryptionAvailable() renvoie true. La délégation à l'OS est donc
possible depuis un plugin tiers, sans repli sur keytar.
#V4 levé également : isEnabled=false au moment
de la pose du patch, alors que le coffre existe dans config.yaml, prouve que le patch
précède ConfigService.init() — donc aussi la pop-up du cas « config chiffrée » (cas non
observé directement, la machine de test étant en config non chiffrée). Le comportement natif est
resté intact : deux appels réels à getPassphrase() depuis decrypt(), le
premier avec saisie utilisateur (~7 s), le second servi par le cache natif (1 ms).
Prochaine étape : implémenter le vrai service de déverrouillage. |
| 2026-07-28 | n/a (pas encore committé) | Analyse statique du chargeur de plugins de Tabby — #V7
levé sans lancer Tabby. initModuleLookup() (dans app.asar) ajoute
<app.asar>/node_modules au NODE_PATH puis force
Module._initPaths() ; l'en-tête de l'archive confirme la présence de
@electron/remote et de keytar parmi ses 351 modules. Le risque jugé le
plus élevé du projet tombe. Deux trouvailles incidentes documentées au passage :
#V8 (liste exacte des modules pré-cachés par Tabby, donc des
externals webpack sûrs) et #V9 (cause racine du piège hérité
#1 : TABBY_PLUGINS.split(':') codé en dur, structurellement incompatible avec les
chemins Windows C:\… — à remonter au projet frère, qui n'en documente que le symptôme).
Enfin, la séquence loadPlugins() → bootstrapModule() combinée au
setTimeout de ConfigService.init() rend
#V4 probablement favorable, mais cela reste à confirmer par le
spike. |
| 2026-07-28 | n/a (pas encore committé) | Scaffold + spike d'observation écrits et compilés.
package.json / tsconfig.json / webpack.config.js /
.gitignore repris du projet frère (mêmes versions verrouillées Angular 15) ;
src/index.ts, src/spike.service.ts, src/spikeLogger.ts.
@electron/remote déclaré en external webpack. Build OK, jonction NTFS de dev créée
dans %APPDATA%\tabby\plugins\node_modules\tabby-better-vault. Le spike
enveloppe getPassphrase sans jamais la substituer et journalise dans
%APPDATA%\tabby\better-vault-spike.log — la passphrase n'est jamais écrite dans ce
journal, même indirectement. En attente d'exécution manuelle par l'utilisateur pour rendre son
verdict sur #V4 et
#V7. |
| 2026-07-28 | n/a | Décision utilisateur : projet lancé. Le contournement par monkey-patch de
getPassphrase est acté malgré son caractère non officiel. Deux éléments ont motivé
l'arbitrage : l'état de l'art (125 plugins tabby-* sur npm ; un seul traite le sujet,
tabby-vault-keepassxc publié le 2026-07-23, et il emploie exactement ce monkey-patch)
et l'absence d'alternative. Positionnement clarifié : le créneau n'est pas
« déverrouiller le Vault » — déjà couvert pour les utilisateurs de KeePassXC — mais le faire sans
gestionnaire tiers, via le keychain natif de l'OS. |
| 2026-07-28 | n/a | Correction d'une erreur d'analyse commise le matin même : le patch d'instance
avait été écarté au motif qu'il forcerait à conserver la passphrase en mémoire toute la session.
Faux — en redéchiffrant à la demande, elle ne vit que le temps de l'appel. Le patch d'instance
devient le mécanisme retenu et l'interception de NgbModal est abandonnée (plus
fragile). Deux nouveaux pièges documentés : #V6 (le cache
natif n'est plus rempli, isOpen() reste faux) et
#V7 (rien ne prouve qu'un plugin tiers puisse résoudre
@electron/remote — inconnue la plus risquée du projet). |
Exécution du garde-fou bloquant décrit dans README.html, sur
tabby-core@1.0.231-nightly.0 (typings npm du projet frère + sources et bundle réels de
l'app installée dans C:\Program Files\Tabby\resources\builtin-plugins\tabby-core).
Aucun code de plugin écrit — lecture seule.
| Date | Hash | Résumé |
|---|---|---|
| 2026-07-28 | n/a (pas de commit) | Résultat de la vérification : partiellement négatif. VaultService
est bien accessible depuis un plugin tiers aux DEUX niveaux (typings et bundle webpack), mais
aucune méthode publique n'injecte le mot de passe maître dans le cache de Tabby — la
condition exacte posée par le garde-fou n'est donc pas remplie. Le design de la
ROADMAP n'est pas réalisable tel quel ; un contournement par interception
du modal a été identifié et documenté comme proposition en attente de décision, pas comme
design validé. Détail des constats dans AI-CONTEXT.html
(pièges #V1 à #V5). |
| Date | Hash | Résumé |
|---|---|---|
| 2026-07-28 | 92be359 |
Constat de rattrapage documentaire : le dépôt Git existe en réalité depuis le 2026-07-26
(Initial commit: governance docs and project design, remote
https://github.com/TooMuhtsh/tabby-better-vault.git), avec .AIRules/
versionné dans le dépôt du plugin lui-même — conforme à la leçon du projet frère. Les mentions
« aucun dépôt Git à ce jour » de README.html et ROADMAP.html étaient
périmées et ont été corrigées ce jour. L'entrée du 2026-07-26 ci-dessous est laissée telle quelle
(journal non réécrit). |
Le déverrouillage automatique du coffre-fort avait été envisagé comme une fonctionnalité de
tabby-better-sidebar, puis explicitement sorti de son périmètre (pas une
fonctionnalité de sidebar, secret utilisateur le plus sensible de l'écosystème) pour devenir ce
projet indépendant. Design discuté et validé pendant les sessions de travail sur le projet frère,
transporté ici tel quel.
| Date | Hash | Résumé |
|---|---|---|
| 2026-07-26 | n/a (pas encore de dépôt Git) | Dossier de travail tabby-better-vault et gouvernance IA (.AIRules/)
créés, sur le modèle de tabby-better-sidebar. Design complet du déverrouillage
automatique porté depuis les décisions actées sur le projet frère (délégation OS via
electron.safeStorage, injection opt-in dans VaultService, purge RAM
immédiate, garde-fou d'échec, pattern d'interfaçage optionnel entre les deux plugins via
l'injecteur Angular partagé). Aucun code écrit — projet au stade design uniquement. |