Dernière revue : 2026-08-14
| Chantier | Statut | Priorité |
|---|---|---|
Vérification de faisabilité (VaultService) |
📋 Terminée — API absente, contournement validé | Absolue |
| État de l'art (plugins existants) | 📋 Fait le 2026-07-28 | Cadrage |
Contournement : patch de getPassphrase |
📋 Mécanisme tranché — acté par l'utilisateur le 2026-07-28 | Fondation |
| Spike d'observation (lève #V4 et #V7) | 📋 Verdict rendu le 2026-07-28 : les deux levés | — |
| Scaffold du plugin | 📋 Base en place, compile | — |
| Déverrouillage automatique | ✅ Fonctionne — validé en conditions réelles le 2026-07-28 | Cœur du plugin |
| Panneau de réglages et politique d'expiration | ✅ Livré — déplacé au registre des livrés le 2026-08-07 | — |
| Journal d'audit et notifications | ✅ Livré — déplacé au registre des livrés le 2026-08-07 | — |
| Publication | ✅ 1.0.2 publiée le 2026-08-14 (hygiène du paquet + notifications) ; 1.0.1 le 2026-08-10 (référencement), 1.0.0 le 2026-08-07. Registre vérifié à chaque fois | — |
| Trois campagnes de vérification Linux | ✅ Closes — déplacées au registre des livrés le 2026-08-06 (option registre-livrés) | — |
| Correctifs issus de la 3ᵉ campagne | ✅ Livré — déplacé au registre des livrés le 2026-08-06 (testé Windows & Linux, macOS accepté comme non testable) | — |
| Journal saturé par les déverrouillages répétés | ✅ Mécanisme validé à l'exhaustif en isolation (2026-08-06) puis confirmé en usage réel le 2026-08-07 — bruit jugé invisible pour l'utilisateur | — |
| Internationalisation (UI traduite, journal en anglais) | ✅ Livrée — déplacée au registre des livrés le 2026-08-06 (testée Windows & Linux, macOS accepté comme non testable) | — |
| Granularité par profil | ✅ Livrée le 2026-08-07 — faisabilité, arbitrages, code, tests et validation le même jour, déplacée au registre des livrés. Le défaut critique attrapé en test est le piège #V30 ; l'entrée de menu côté sidebar reste un chantier de l'autre dépôt | — |
| Interfaçage avec tabby-better-sidebar | ✅ Clos le 2026-08-07 — les deux cas d'usage livrés sur le même pattern (clé de chaîne, contrat dupliqué) : panneau unifié et pont BetterVaultExclusions:v1 de la granularité | — |
| Panneau de réglages unifié « Better Tabby » | ✅ Livré le 2026-08-07 — conçu, codé et validé 25/25 le même jour, déplacé au registre des livrés | — |
Reliquat betterVault: dans config.yaml |
✅ Retiré par l'utilisateur de sa config le 2026-08-07, après vérification qu'aucun code de src/ n'y touchait | — |
| Conformité de la gouvernance à la charte | ✅ Charte fusionnée jusqu'à 20260804-071239 et entretien de cadrage relancé (2026-08-06) — 24 options tranchées, aucune ouverte | Entretien récurrent |
| Registre des livrés | ✅ Sept entrées migrées vers annexes/REALISE.html (trois campagnes, correctifs-c3, i18n, réglages, audit) — aucun chantier non terminal ne reste dans ce tableau | — |
Journal en .log |
✅ Fait (2026-08-06) — sans conversion rétroactive, AI-HISTORY.html gelé ; validateur lint:history depuis le 2026-08-14 | — |
| Notifications : ancrage et couleurs | ✅ Livré et validé par l'utilisateur le 2026-08-14 — bleu pour l'information, rouge réservé aux alertes, pastille ancrée au pane focalisé, repli en toast de fenêtre sans onglet, notifications signées « Better Vault » | — |
| Hygiène du paquet et de l'outillage (5 points GitHub) | ✅ Lot livré le 2026-08-14, le jour de sa consignation — strip des maps en production (0 map au pack --dry-run), notices tierces dans files[], CHANGELOG créé, audit fix nu (19 → 16) ; effectif sur npm à la prochaine publication, non déclenchée. Restent en surveillance : vulnérabilités Angular/pug, apply-loader, webpack-cli@4 | — |
Menée sur tabby-core@1.0.231-nightly.0, en lecture seule, aux deux niveaux exigés
(typings npm et bundle webpack compilé), complétés par les sources src/ que
l'app installée embarque. Constats détaillés et numérotés en
AI-CONTEXT.html § Anatomie du Vault.
| Question posée par le garde-fou | Réponse |
|---|---|
VaultService existe et est atteignable depuis un plugin tiers ? |
✅ Oui, aux deux niveaux. Typings : réexporté par
typings/api/index.d.ts, lui-même repris par index.d.ts via
export * from './api'. Bundle : présent dans le bloc de réexport webpack. Aucune
augmentation de type nécessaire (#V1). |
| Expose-t-il une méthode d'injection programmatique du mot de passe maître ? | ❌ Non. Le cache est une variable de portée module
(_rememberedPassphrase) explicitement rendue inaccessible par Tabby. Aucune méthode
publique ne l'initialise, sauf getPassphrase() qui ouvre obligatoirement le modal
(#V2). |
| Peut-on contourner en passant la passphrase à chaque appel ? | ❌ Non. Les consommateurs internes de Tabby appellent le Vault sans argument et retombent toujours sur le cache natif (#V3). |
La condition exacte posée dans README.html — « expose une méthode d'injection programmatique du mot de passe maître » — n'est pas remplie. Le protocole du projet impose donc de remonter le blocage plutôt que de continuer : le design de la section Déverrouillage automatique n'est pas réalisable tel quel, et le plugin ne doit pas être scaffoldé avant arbitrage.
Un contournement techniquement viable existe (ci-dessous), mais il repose sur un monkey-patch d'une API interne de Tabby. C'est un changement de nature du projet, pas un simple ajustement d'implémentation — d'où la décision demandée à l'utilisateur avant toute suite.
Recherche exhaustive du registre npm avec la requête exacte du gestionnaire de plugins intégré
(keywords:tabby-plugin, cf. tabby-plugin-manager/src/services/) :
125 plugins publiés, dont 3 touchent aux secrets.
| Plugin | Sujet | Rapport à ce projet |
|---|---|---|
tabby-vault-keepassxc v1.0.1 (2026-07-23) |
Déverrouillage automatique du Vault via KeePassXC (Browser Integration, NaCl + socket natif). | Concurrent direct, publié 2 jours avant la création de ce projet. Source du secret différente : gestionnaire tiers, pas l'OS. |
tabby-keepass-ssh v1.0.4 |
Auto-remplissage des mots de passe SSH et TOTP via KeePass. | Sujet voisin, pas le Vault. |
tabby-ssh-auth-socket v1.0.0 |
Chemin de socket d'agent SSH (Secretive, 1Password). | Hors sujet. |
Aucun plugin existant ne délègue à l'OS. Le créneau de ce projet n'est donc pas
« déverrouiller automatiquement le Vault » (déjà couvert depuis le 2026-07-23 pour les utilisateurs
de KeePassXC) mais « le faire sans aucun gestionnaire tiers, via le keychain natif
de la machine ». Le pitch du README.md public doit refléter cette différence, pas
prétendre à l'antériorité.
tabby-vault-keepassxc nous apprendSon bundle npm a été lu (dist/services/vault-passphrase-bridge.service). Il
confirme indépendamment le diagnostic de la
vérification de faisabilité : faute d'API d'injection, il monkey-patche
la méthode sur l'instance du service.
install(vault, client, _settings) {
this.originalGetPassphrase = vault.getPassphrase.bind(vault);
this.serializedFetch = serializeAsync(() => this.resolvePassphrase());
vault.getPassphrase = () => this.serializedFetch();
}
Installé depuis le constructeur de son NgModule, via
this.injector.get(core.VaultService) — avec un garde
if (!core.VaultService) return et un try/catch englobant. Aucun cache de
passphrase dans le bundle (malgré ce que suggère son README) : il réinterroge KeePassXC à chaque
appel, et retombe sur originalGetPassphrase en cas d'échec.
getPassphraseQuestion posée : accepte-t-on de piloter le Vault par un mécanisme non officiel, alors que Tabby a délibérément fermé l'accès programmatique au mot de passe maître ? Réponse : oui, au vu de l'état de l'art (un plugin publié fait déjà exactement cela) et de l'absence d'alternative. Le projet est donc lancé.
Remplacer getPassphrase sur l'instance de VaultService
obtenue via l'injecteur Angular, en conservant l'originale pour le repli. Notre implémentation
déchiffre le token via l'OS, retourne la passphrase, et ne conserve rien.
Une première version de cette section écartait le patch d'instance au motif qu'il
« obligerait à garder le mot de passe en clair dans une closure pendant toute la session », et lui
préférait une interception de NgbModal.open(). C'est faux :
rien n'oblige à mettre en cache. En redéchiffrant via l'OS à chaque appel — ce que fait
tabby-vault-keepassxc, qui réinterroge sa source à chaque fois — la passphrase ne vit
que le temps de l'appel, ce qui respecte pleinement l'invariant de purge RAM. Le patch d'instance
est donc retenu, et l'interception de modal abandonnée (plus fragile : elle
imposait d'identifier UnlockVaultModalComponent par son nom, cette classe n'étant pas
réexportée — cf. #V1).
_rememberedPassphrase reste
null, donc isOpen() renvoie toujours false
(#V6) et le réglage natif « remember for » devient sans
effet. C'est à nous d'assumer la politique de durée — voir
#V5.originalGetPassphrase et y retomber à la
moindre erreur : OS indisponible, token absent, token obsolète après changement du mot de passe
maître. Jamais d'erreur bloquante — la pop-up native doit reprendre la main de façon
transparente.getPassphrase dans
serializeFunction au niveau du constructeur. Notre remplaçante doit sérialiser ses
propres appels pour ne pas déclencher plusieurs déchiffrements concurrents.| Inconnue | Verdict |
|---|---|
| #V7 — accès à l'OS | require("@electron/remote") OK, remote.safeStorage présent,
isEncryptionAvailable() → true. Délégation à l'OS
possible, sans repli sur keytar. |
| #V4 — timing | isEnabled=false à la pose du patch alors que le coffre existe : nous passons
avant ConfigService.init(), donc avant la pop-up même en config
chiffrée (cas non observé directement — machine de test en config non chiffrée). |
Le comportement natif est resté intact pendant tout le test : deux appels réels à
getPassphrase(), le premier avec saisie utilisateur, le second servi par le cache natif
de Tabby.
Le reste de cette section décrit le spike tel qu'il a été conçu, conservé pour mémoire.
Objectif : lever les deux inconnues empiriques sans jamais substituer la passphrase
lors des premières exécutions. Le spike enveloppe getPassphrase (appel de
l'originale + journalisation) au lieu de la remplacer.
Il n'existe pas d'équivalent « données jetables » pour un coffre-fort : le test porte forcément sur la vraie config de l'utilisateur. Une substitution qui échoue au mauvais moment peut le verrouiller hors de sa propre configuration. La règle de prudence du workspace s'applique donc en version renforcée : première exécution en lecture seule.
getPassphrase, la valeur de config.encrypted et la pile d'appel — pour
savoir si notre patch est en place avant le premier appel, y compris en config chiffrée.require('@electron/remote') puis safeStorage.isEncryptionAvailable()
depuis le plugin, et journaliser le résultat sans rien chiffrer.injector.get(VaultService) rend bien l'instance déjà
construite et que notre enveloppe se pose au-dessus de celle de serializeFunction.Si la config de l'utilisateur est chiffrée (config.encrypted === true), la pop-up
est déclenchée très tôt au démarrage par ConfigService.init()
(#V4). Il n'est pas établi qu'un plugin tiers
puisse installer son interception avant ce moment — un plugin a lui-même besoin de
ConfigService, ce qui rend l'ordre d'initialisation défavorable.
Si la config n'est pas chiffrée, le problème ne se pose pas : la pop-up n'arrive qu'à la première demande de secret, largement après le chargement des plugins. Il est donc possible que le plugin ne soit réalisable que dans ce second cas — ce qui réduirait sa portée et mérite d'être su avant d'écrire du code.
Vérification requise : Tabby lancé avec --debug --remote-debugging-port=9333
(piège hérité #8), sur un profil de test, pour observer l'ordre réel d'initialisation. Non fait à ce
jour.
safeStorageLargement levé le 2026-07-28 par lecture du chargeur de plugins (détail en
#V7) : Tabby ajoute <app.asar>/node_modules
au NODE_PATH puis appelle Module._initPaths(), et
@electron/remote comme keytar sont bien présents dans cette archive. Un
plugin tiers peut donc les résoudre. Reste à confirmer empiriquement que safeStorage
est effectivement proxifié par @electron/remote et que
isEncryptionAvailable() renvoie true — c'est dans le périmètre du
spike.
Pas encore commencé. Probablement la même mécanique que tabby-better-sidebar à son
lancement (voir son historique pour
le détail de ce qui avait été fait) :
package.json/tsconfig.json/webpack.config.js/LICENSE.%APPDATA%\tabby\plugins\node_modules\tabby-better-vault
(pas TABBY_PLUGINS, cassé sur Windows).https://github.com/TooMuhtsh/tabby-better-vault), avec .AIRules/
directement versionné dedans — leçon du projet frère appliquée dès le départ.Le reste du scaffold est explicitement gelé tant que la décision sur le contournement n'est pas prise.
Le flux ci-dessous suppose une injection programmatique du mot de passe dans
VaultService ; la vérification de faisabilité a montré que
cette API n'existe pas. Les objectifs et les règles de sécurité de
cette section restent entièrement valables — seule l'étape 2 du flux d'injection est à remplacer, par
le contournement proposé si celui-ci est retenu.
Objectif : éviter la ressaisie du mot de passe maître de Tabby à chaque démarrage, en déléguant le chiffrement à l'OS plutôt qu'en stockant le mot de passe en clair.
electron.safeStoragelibsecret) via GNOME Keyring/KWallet.VaultService de Tabby, avant que la pop-up native de saisie n'apparaisse.config.encrypted (#V4) — elle n'apparaît pas
systématiquement au démarrage. À remplacer par le contournement.Énoncé d'origine : « la variable contenant le mot de passe déchiffré doit être
immédiatement écrasée (= null) dès que l'injection dans le moteur de Tabby est
validée ».
Pourquoi il fallait le reformuler : il n'y a plus d'étape d'injection. Le plugin ne pousse pas un mot de passe vers Tabby — il est la source que Tabby interroge, et la valeur rendue est une chaîne JS immuable, qu'on ne peut ni écraser ni remettre à zéro. Appliquer la règle à la lettre n'a donc plus de sens.
Énoncé applicable, de portée identique : le mot de passe en clair ne doit survivre à aucun appel. Il ne vit que dans les variables locales de la fonction qui le résout ; il n'est jamais placé dans un champ d'objet, jamais mis en cache, jamais journalisé (sa longueur non plus), jamais écrit sur disque autrement que chiffré par l'OS. La contrainte est la même — fenêtre d'exposition minimale — seule sa formulation change.
Conséquence assumée : puisque rien n'est mis en cache, chaque appel redéchiffre le jeton via le keychain. C'est le prix de la règle, et il est négligeable (DPAPI/Keychain répondent en quelques millisecondes).
Fichier séparé : better-vault-token.bin, à côté de
config.yaml dans le dossier de configuration de Tabby. Jamais dans
config.yaml.
Question posée par l'utilisateur : quel risque à ce que le jeton soit exportable, puisqu'il n'est
utilisable que sur une seule machine au monde ? Analyse : il a raison sur la
confidentialité. Un blob chiffré par safeStorage est inexploitable ailleurs —
sous Windows, DPAPI le lie même au compte utilisateur, pas seulement à la machine. Le risque de
fuite du secret par export est quasi nul (à l'exception de Linux, voir plus bas).
Ce qui tranche est fonctionnel, pas sécuritaire. La synchronisation de config de
Tabby (tabby-settings/src/services/configSync.service.ts) envoie l'intégralité de
config.yaml sauf la seule clé configSync, et
writeConfigDataFromSync() écrase le fichier local par la version distante. Un jeton
propre à une machine placé là déclencherait une boucle de destruction mutuelle :
config.yaml, échoue à le déchiffrer, le purge
(garde-fou normal), sauvegarde → resynchronise.Et ainsi de suite à chaque cycle. Ce n'est pas un scénario d'attaque, c'est le fonctionnement nominal dès qu'on a deux machines synchronisées.
Ce fichier ne doit donc jamais être réintégré à config.yaml ni ajouté à un
quelconque mécanisme d'export — même discipline que pour les workspaces de
tabby-better-sidebar
(son ROADMAP.html, où
l'export ne porte jamais sur les données sensibles).
basic_text refuséSur Linux, safeStorage retombe sur un backend basic_text quand aucun
service Secret n'est joignable sur le bus de session. Dans ce mode, la clé est dérivée d'un mot de
passe codé en dur, avec le sel fixe "saltysalt" et une
seule itération de PBKDF2 : le chiffrement est décoratif et le jeton lisible par quiconque.
Cette section affirmait que le repli se produisait quand XDG_CURRENT_DESKTOP ne
figurait pas dans la liste d'Electron, et citait Sway, i3, Hyprland, bspwm et awesome. C'est
faux : mesuré en session i3 sur Ubuntu Desktop, le backend retenu reste
gnome_libsecret dès lors que gnome-keyring-daemon revendique
org.freedesktop.secrets. Ce qui compte est la disponibilité du service Secret,
pas la détection du bureau — un utilisateur i3 disposant d'un trousseau obtient donc le chemin
nominal, pas un refus abusif. Détail et mesures : #V15.
Le plugin refuse donc de s'activer si
getSelectedStorageBackend() renvoie basic_text, plutôt que d'offrir une
fausse impression de sécurité. C'est le seul cas où l'argument « le jeton est inexploitable
ailleurs » tombe.
Sur Ubuntu Desktop, Tabby 1.0.235 / Electron 38.8.6. Refus confirmé avec
tabby --password-store=basic ; acceptation confirmée en session GNOME normale,
trousseau déverrouillé. La campagne a révélé deux défauts, corrigés depuis : le
motif expliquant basic_text était inatteignable
(#V15), et les réglages restaient actionnables alors que
rien ne pouvait fonctionner (#V16).
Le cas du trousseau verrouillé, alors annoncé comme « non exercé, rattrapé par le repli natif », a depuis été couvert par une campagne indépendante — et l'annonce était fausse. Voir Campagne de vérification indépendante, dans le registre des livrés.
Onglet « Better Vault » dans les paramètres de Tabby, expiration du jeton en trois modes, réglages par machine. Rien n'y est resté ouvert lors de la relecture du 2026-08-07 qui a corrigé le tableau récapitulatif et le § Registre des livrés ci-dessous — les deux le donnaient à tort pour non terminal. Détail intégral, repris tel quel, dans le registre des livrés.
Horodatage absolu par ligne, niveaux INFO/WARN/CRIT,
révocation manuelle et changements de configuration tracés, rétention réglable doublée d'un plafond
de taille, boutons « Ouvrir » et « Purger ». Un seul point reste ouvert, reporté sans urgence : voir
le registre. Cette section portait jusqu'au 2026-08-07 une liste Ce qui manque réellement
qui recopiait exactement les cinq points ci-dessus comme non implémentés — contredite par le callout
« Réalisé » juste au-dessus d'elle dans la version précédente de ce document, et par
AI-HISTORY.html § « Journal d'audit et notifications (2026-07-28) », qui
atteste leur livraison par 10ad983 le jour même. Vérification indépendante sur le code
actuel (src/logger.ts, src/components/settingsTab.component.ts) confirmée :
la liste était fausse, retirée plutôt que corrigée sur place. Détail intégral, repris tel quel, dans le
registre des livrés.
Dépôt GitHub public depuis le 2026-07-28 :
github.com/TooMuhtsh/tabby-better-vault,
avec README.md (anglais) et README.fr.md (français).
tabby-better-vault@1.0.2 : hygiène du paquet et notificationsPublication déclenchée par l'utilisateur (« publie la 1.0.2 »), numéro choisi par lui. Elle rend
effectifs sur npm les deux chantiers du jour : l'hygiène du paquet
(la source map ne part plus — 239,6 Ko décompressés contre 756 pour la 1.0.1,
27 fichiers, THIRD-PARTY-NOTICES.md embarqué) et les
notifications (couleurs par sévérité, ancrage, signature « Better Vault »). Protocole appliqué,
leçon du projet frère comprise : bump + entrée CHANGELOG commités avant le
npm publish, si bien que le gitHead du registre (f64665e)
pointe sur le bon commit. Registre vérifié après coup : latest = 1.0.2. Tag
v1.0.2 et release GitHub créés dans la foulée ; build de développement rendu à la
jonction après la passe.
tabby-better-vault@1.0.1 : référencement npm amélioréRepublication déclenchée par l'utilisateur pour corriger un défaut de référencement : la
1.0.0 ne portait que le seul mot-clé tabby-plugin — suffisant pour le
gestionnaire de plugins de Tabby, mais quasi invisible dans la recherche npm. La 1.0.1
porte quinze mots-clés (tabby-plugin, tabby, terminal,
vault, keychain, keyring, passphrase,
credentials, secrets, ssh, security,
safestorage, dpapi, libsecret, electron), sans
aucun changement de code — premier incrément de patch de la politique semver ci-dessous.
Particularité consignée après coup (2026-08-14) : le bump et les mots-clés ont
été commités et poussés par l'utilisateur dans la foulée de la publication (7dba0f8,
avec les tags v1.0.0 et v1.0.1 — premiers tags du dépôt), mais depuis un
autre clone que le poste de travail habituel, resté en retard sans le savoir : un
git status local se comparait à des refs non rafraîchies et affichait à tort « à
jour ». Leçon : toujours git fetch avant de conclure sur l'état du dépôt.
tabby-better-vault@1.0.0 sur npmPublication déclenchée par l'utilisateur (« Corrige et publie »), après le rattrapage des
documents publics : la granularité par profil entrait dans README.md,
README.fr.md (fonctionnalités, utilisation, installation par le gestionnaire de
plugins de Tabby) et docs/ARCHITECTURE.md (section dédiée, limites dites sans
détour). Version restée à 1.0.0, décidé par l'utilisateur le jour même :
la première publication embarque tout, la politique semver ci-dessous ne s'applique qu'aux
versions publiées.
prepublishOnly
lançait le build de développement (le mode dépend de NODE_ENV, jamais posé)
— un build:prod (--mode production, qui passe par-dessus la config
objet) publie désormais un bundle minifié ; et dist/ traînait trois
.d.ts orphelins d'anciennes sources, reconstruit à neuf avant l'empaquetage.1.0.0 en
latest, keywords: tabby-plugin (découverte par le gestionnaire de
plugins), author présent (piège hérité #2 / #V29), README anglais repris. Paquet :
27 fichiers, 212 Ko compressés, dist seul + READMEs + licence.author était déjà là, files ne publie que dist —
les deux vérifications prévues par cette fiche tenaient.Diffusion, le soir même (2026-08-07) : le paquet est apparu dans le gestionnaire
de plugins de Tabby quelques heures après la publication — la crainte d'un délai d'indexation ne
s'est pas matérialisée. Dans la foulée : six topics GitHub ajoutés au dépôt (tabby-plugin,
tabby, terminal, ssh, vault,
electron, il n'en portait aucun) ; annonce postée par l'utilisateur dans les
Discussions de Eugeny/tabby (catégorie General) ; réponses postées dans plusieurs
issues et discussions du dépôt amont qui demandaient expressément ce déverrouillage automatique —
recensées ce jour-là, les plus directes étant
#10256 (Windows Hello),
#6344 (Remember Vault password),
#7163 et
#7976. Les annonces assument le
mécanisme non contractuel et le périmètre de test « Windows et Linux, macOS non testé », dans les
mêmes termes que les documents publics.
CLAUDE.md, tools/ et .htmlvalidate.json désindexés
(git rm --cached + .gitignore) : outillage de session de dev, sans
intérêt pour qui clone le dépôt pour l'utiliser ou le compiler — restent en local.
ISSUE-tabby-titlebar-overlay.md supprimé pour de bon (bug résolu, concernait Tabby, pas
ce plugin). .AIRules/ reste public et actif, sans changement — décision explicite de
l'utilisateur.
118 trailers Co-Authored-By: Claude ... retirés de l'historique de
master par git filter-branch --msg-filter puis force-push, pour
appliquer rétroactivement la politique d'attribution du 2026-08-07 (voir plus bas) à tout ce qui
avait été commité avant elle. 10 branches déjà fusionnées supprimées dans le même geste (locales et
origin). Sauvegarde : tag local backup-pre-rewrite-20260807, non poussé.
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 antérieur à cette date est cassé. Conséquence directe et attendue :
tout hash de commit cité ailleurs dans ce document et daté d'avant le 2026-08-07 a changé — les
quatre occurrences trouvées ont été corrigées sur place le jour même.
Semver standard, appliqué explicitement à partir de 1.0.0 :
x.x.1.x.1.x, et remet le
patch à zéro.Le plugin repose sur un patch d'une API interne de Tabby (#V2) et manipule le secret le plus sensible de l'utilisateur. Publier, c'est engager cette responsabilité auprès d'inconnus, sur des configurations que nous ne maîtrisons pas. Le garde-fou Linux, lui, n'est plus un pari : il a été exercé dans les deux sens le 2026-07-29 (voir ci-dessus), et les deux défauts que cette vérification a mis au jour sont corrigés. Le cas du trousseau verrouillé a reçu son correctif le même jour et a été validé sur Linux (deuxième campagne).
Ce qui bloque encore la publication n'est plus le gel, c'est le rythme des découvertes. La journée du 2026-07-29 a produit trois défauts corrigés, puis deux défauts nés de ces correctifs — dont un de sévérité haute qui annulait la garantie centrale. Les correctifs de D1 et D2 ne sont pas revalidés. Publier maintenant, ce serait diffuser à des inconnus un code dont la dernière révision n'a jamais été exercée sur la seule plateforme qui sait la mettre en défaut.
Condition de levée, explicite : la troisième campagne passée sans défaut nouveau de sévérité haute, D4 mesuré dans un sens ou dans l'autre. Si elle produit à nouveau des régressions issues de ses propres correctifs, ce n'est plus un correctif qui manque mais une revue de conception — et la publication attend d'autant plus.
Verdict au 2026-08-01 : la condition n'est pas remplie, mais pas pour le motif prévu. Les correctifs D1 et D2 tiennent, et aucun des dix défauts trouvés n'en est une régression — ce sont des zones jamais exercées. La clause « revue de conception » ne se déclenche donc pas au sens où elle était écrite. Elle se déclenche pour une autre raison : D4 est confirmé jusqu'en usage spontané et la destruction du jeton sur annulation d'invite touche la raison d'être du plugin. La publication attend donc les quatre lots de correctifs et le sort des deux sujets de conception.
Verdict au 2026-08-06 : la condition explicite est désormais remplie, sur une plateforme.
D4 est mesuré, cette fois pour de vrai : structurellement impossible par
l'interface de Tabby sur Linux, code à l'appui, et le seul autre vecteur connu (profils partagés)
supprimé par configDir(). La mesure a fait remonter un défaut réel — le témoin effacé
sans vérification — et il a été corrigé le jour même (84d7a98), pas laissé pour plus
tard. C'est la première fois qu'une campagne de ce projet ferme la boucle jusque-là : mesure, défaut
trouvé, correctif, sans report.
Restait, à cette date, le scénario « deux fenêtres, un processus principal » — le seul chemin
restant vers le défaut du témoin, exposé sur macOS via « New window », jamais exercé faute de
machine. Le correctif lui-même s'applique à toutes les plateformes, sans code spécifique à Linux ;
ce qui manquait n'était donc pas un correctif à écrire mais une mesure à faire. Windows n'a validé
que configDir() en conditions réelles, jamais le garde-fou lui-même — DPAPI n'ayant pas
d'équivalent au trousseau verrouillé de Linux, la question de savoir si un scénario comparable y
existe reste ouverte, pas tranchée par son absence de mention ici.
Verdict au 2026-08-06 (second) : la condition est considérée remplie — macOS accepté comme non testable, pas comme validé. Décision de l'utilisateur : ce projet n'a jamais eu accès à une machine macOS, sur aucune campagne, et attendre cette mesure revenait à bloquer la publication sur une inconnue permanente plutôt que temporaire. Le plugin est publié comme testé sur Windows et Linux — le README doit le dire dans ces termes, pas prétendre à une couverture macOS qui n'a jamais été vérifiée. Si le scénario « deux fenêtres, un processus » se confirme un jour sur macOS, c'est un défaut réel à corriger, pas une remise en cause de cette décision : l'acceptation porte sur l'absence de preuve, pas sur une conviction que le risque n'existe pas.
Les trois campagnes menées du 2026-07-29 au 2026-08-01 sont closes ; leur détail, repris tel quel, vit désormais dans le registre des livrés. Leurs dix défauts trouvés hors périmètre vivent comme chantier actif : Correctifs issus de la 3ᵉ campagne, ci-dessous.
Quatre lots (jeton détruit, écriture du fichier, lisibilité, outillage) clos le 2026-08-05 ;
configDir() et le garde-fou validés en conditions réelles par la campagne 5 le
2026-08-06 ; macOS accepté comme non testable le même jour — condition de publication remplie.
Détail intégral, repris tel quel, dans le registre des
livrés.
Le protocole tri-postes ci-dessous n'a pas été exécuté : la VM Ubuntu prévue est
restée hors d'atteinte. La validation des trois correctifs de suivi qui manquaient a eu lieu sur
l'installation Windows réelle de l'utilisateur, par un chemin différent — l'injecteur Angular plutôt
que l'aveuglement en boîte noire à trois postes. Voir le callout de clôture sous
#correctifs-c3 et le détail dans AI-HISTORY.html. Le
protocole ci-dessous reste utile tel quel pour une prochaine campagne multi-postes en boîte noire —
rien de ce qu'il documente n'est devenu faux, il n'a simplement pas servi cette fois.
Les VM sont dupliquées et les trois campagnes tournent en même temps, chacune sur la branche de son lot : A pour le jeton détruit, B pour l'écriture du fichier, C pour la lisibilité. Le lot 4 n'a pas de poste — il est déjà validé et ne touche pas le runtime.
Le matériel vit dans .tempfiles/, hors dépôt comme celui de la campagne 3 : un dossier
d'opérateur qui porte les hypothèses et la grille de dépouillement, trois prompts aveuglés, et trois
paquets construits. Rien de tout cela n'est versionné — le dossier d'opérateur ne doit
jamais atteindre une VM.
.AIRules/ du clone n'aveugle plus rien
Le dispositif des campagnes précédentes — cloner sans .AIRules/, CLAUDE.md,
.tempfiles/ ni .git — est caduc. Mesuré le 2026-08-02 :
le bundle construit en mode développement contient les commentaires du code en clair,
et ceux des correctifs disent littéralement ce qu'ils corrigent et à quelle campagne ils répondent ;
dist/index.js.map porte l'intégralité des sources commentées ; trois .d.ts
sont révélateurs. Le code raconte ses propres correctifs — et ce projet commente
abondamment, ce qui est une qualité partout ailleurs.
D'où le régime retenu : boîte noire. L'instance ne reçoit aucune source et ne
construit rien. Elle reçoit dist/index.js construit en production, plus un
package.json, et teste ce que le plugin fait. C'est le seul aveuglement réel, et il est
plus proche de l'usage.
Un troisième canal est apparu à la construction : le CSS est embarqué comme chaîne JS, donc
les commentaires SCSS survivent à la minification, avec leurs renvois vers
.AIRules/ et des numéros de pièges. Ils ne parlent d'aucun correctif, mais mettraient une
instance curieuse sur la piste d'un dépôt public où tout est écrit. Un caviardage ciblé les retire,
comme le package.json est réduit pour ne pas porter l'URL du dépôt.
Le protocole imposait le paquet .deb, hérité de la campagne 3. Or il exige les
droits d'administration, donc une saisie du mot de passe du compte — que le protocole ne
déclarait nulle part, alors qu'il interdit par ailleurs à l'opérateur de saisir quoi que ce soit
d'imprévu. Le protocole se contredisait lui-même.
C'est l'instance du poste B qui l'a résolu spontanément, en prenant l'archive portable
tabby-1.0.235-linux-x64.tar.gz — même binaire officiel, SHA-256 relevé, aucun privilège
requis — et en déclarant ses deux écarts (--no-sandbox, rendu nécessaire par un
chrome-sandbox non setuid et le confinement des espaces de noms d'Ubuntu 24.04, et
--enable-logging). C'est désormais la méthode imposée aux trois postes.
Ce qu'il faut en retenir dépasse le cas : une contrainte de protocole qui oblige à en violer une autre est un défaut du protocole, pas de celui qui l'exécute.
Le protocole les réduit désormais explicitement, en séparant ce qui est irréductible de ce qui ne
l'était que par habitude. Irréductible : la pop-up de coffre appartient au moteur de
rendu de Tabby, l'injection clavier ne l'atteint pas. Ne l'est pas : le trousseau se
verrouille et se déverrouille en ligne de commande (busctl,
gnome-keyring-daemon --replace --unlock qui lit la phrase sur stdin) ; les
fenêtres d'authentification du système sont GTK et non Chromium, donc peut-être
pilotables — la mesure qui fonde la règle portait sur le renderer de Tabby et ne dit rien de GTK ; et
un état sauvegardé se restaure au lieu de se reconstruire, ce qui supprime la plupart des ressaisies.
Le garde-fou est ce qui compte : ces raccourcis sont interdits dans tout scénario dont l'invite d'authentification est l'objet. La supprimer ne rendrait pas le scénario plus rapide, elle le rendrait vide — en produisant un rapport d'apparence normale. Le mot de passe du compte est déclaré dans les prompts, la VM étant jetable et restaurée par instantané.
umount
ne survit pas aux redémarrages que les scénarios exigent, et un partage ouvert est un chemin vers
l'hôte que le rapport ne mentionnera pas.Signalé par l'utilisateur à partir de la console de Tabby : la ligne INFO vault unlocked
from the system keychain se répète en rafale, jusqu'à quatre fois par seconde pendant des
salves de trente secondes à une minute. Comptage sur better-vault.log : 262
déverrouillages le 2026-08-03, 167 la veille, 26 l'avant-veille.
Sur une configuration chiffrée, ConfigService.save() passe par
maybeEncryptConfig(), qui appelle vault.load() — donc
decrypt(), donc getPassphrase() — puis vault.encrypt(),
qui rappelle getPassphrase(). Deux appels par sauvegarde de configuration.
Deux faits le confirment : les lignes du journal arrivent systématiquement par paires, et le
mtime de config.yaml coïncide à la seconde près avec la dernière ligne de la
rafale. Rythme observé : environ deux sauvegardes par seconde.
Sans le plugin, ces mêmes appels sont servis en silence par le cache natif
_rememberedPassphrase, que le chemin du déverrouillage automatique ne remplit jamais
(#V6) : le premier appel ouvre le modal, les suivants
retournent la valeur mémorisée sans un mot. Notre remplaçante, elle, refait le cycle complet et le
journalise à chaque fois. Le bruit est un symptôme que nous rendons visible, pas un défaut
que nous créons.
better-vault.json
(readSettings()), un safeStorage.decryptString, un
console.log et un appendFileSync synchrone. Le PBKDF2 de
vérification, lui, est bien évité par tokenVerified — ce garde-là tient.save() sur une
configuration chiffrée fait deux dérivations PBKDF2 de 100 000 itérations SHA-512
(une pour decryptVault, une pour encryptVault, qui régénère son sel) et
réécrit les 49 Ko de config.yaml. Deux fois par seconde. Cela se produirait à
l'identique sans le plugin, silencieusement — c'est le vrai problème, et il est en
amont.HARD_SIZE_LIMIT) est exactement le
garde-fou que ce cas anticipait : il protège, mais au prix de l'écrasement des lignes anciennes,
qui sont précisément celles qui portent l'information.Aucun fichier de src/ n'appelle config.save() ni n'injecte
ConfigService — vérifié par recherche exhaustive sur l'arborescence. Le plugin
n'enclenche donc pas les sauvegardes qu'il subit. Reste à identifier qui les enclenche : ce
n'est pas nécessairement du ressort de ce projet, mais le pont est le seul point d'observation dont
on dispose sur cet appel.
logger.ts, pas dans
vaultBridge.service.ts : un mécanisme de temporisation réutilisable, sur le modèle de
unlockAnnounced mais côté journal. La première ligne de déverrouillage
d'une session reste écrite en clair — elle porte l'information « quand le coffre a été ouvert », qui
est l'une des deux raisons d'être du journal — et les répétitions sont comptées puis résumées en une
ligne. Corrige la console et le fichier au même endroit, puisque les deux passent par
write().debug), et là seulement, pour
nommer l'appelant qui sauvegarde la configuration deux fois par seconde. Le mode observation est le
bon porteur : il existe déjà pour regarder sans agir.logger.ts étouffe désormais les répétitions immédiates d'une même ligne, pour
log/warn/crit indifféremment : la première occurrence part en
clair, les suivantes sont comptées puis résumées
(« repeated N more times over the following Ys ») après 3 s d'accalmie — pas avant, pour
ne pas fragmenter un dénombrement en cours. Un purge() pendant une rafale en cours de
comptage abandonne le résumé au lieu de le reporter dans le fichier qui vient d'être vidé.
En mode observation, resolve() capture désormais new Error().stack avant
tout await — donc encore sur le fil d'appel synchrone de Tabby, qui porte donc encore ses
propres cadres — pour nommer l'appelant responsable d'une rafale, plutôt que d'en observer seulement
le symptôme. Cette capture de pile, elle, n'a été exercée ni isolément ni en réel :
couplée à l'injecteur Angular (this.settings, previewOnly()), elle ne se
prête pas au même test isolé que le reste de ce fichier.
Validé par test isolé pour la temporisation elle-même : module réel compilé et
exécuté hors Electron, APPDATA redirigé vers un journal jetable. Onze cas, tous
conformes : rafale de cinq lignes identiques → une seule écrite immédiatement, résumé après
l'accalmie ; une ligne différente flushe le résumé en cours sans attendre son propre délai ;
purge() en pleine rafale abandonne le résumé plutôt que de le reporter dans le fichier
qui vient d'être vidé.
Ce que ce test ne couvre PAS : le déclencheur réel, c'est-à-dire Tabby rappelant
véritablement getPassphrase() deux fois par seconde via ConfigService sur
une session à configuration chiffrée — le test appelle log() directement, il ne fait pas
tourner le pipeline réel. Contrairement au garde-fou, ce n'est pas spécifique à Linux
: reproductible sur Windows dans un profil jetable avec un mot de passe de test, sans toucher aux
données réelles de l'utilisateur — pas fait à ce jour, faute de session sous la main au moment
d'écrire ce correctif.
Confirmation en usage réel, 2026-08-07 : l'utilisateur rapporte le bruit comme invisible dans son usage quotidien depuis le correctif du 2026-08-06. Ce n'est pas la mesure protocolaire (rafale authentique capturée et comptée) que ce document appelait encore hier, mais c'est le jugement qui compte en pratique — chantier considéré clos de ce fait, la capture de pile en mode observation restant un outil disponible si le symptôme revenait un jour.
Élargir la fenêtre de this.pending — la garder quelques centaines de millisecondes
au lieu de la libérer dans le finally — absorberait les rafales et supprimerait aussi
bien le bruit que les allers-retours vers le trousseau. C'est exactement ce que l'invariant
interdit : la promesse retenue contient le mot de passe en clair, donc le conserver entre
deux appels, c'est le mettre en cache. Le coût par appel est le prix assumé de la règle « le mot de
passe en clair ne survit à aucun appel ». Il ne se négocie pas contre du confort de journal.
Deux axes, décidés ensemble par l'utilisateur le 2026-08-14 :
#b3261e) : le
toast d'enregistrement de la clé (toastr.info, vaultBridge.service.ts),
celui du trousseau suspendu (toastr.warning) et la pastille de déverrouillage
(inlineToast.ts) partagent la même classe better-vault-toast et le même
fond dans toast.scss. Une information (« clé enregistrée », coffre déverrouillé)
ressemble donc à une erreur — c'est la confusion à lever : couleur distincte pour l'information,
le rouge restant aux avertissements et erreurs.tab-body.content-tab-active (#V13) : elle
vise l'onglet actif, pas le panneau actif d'une vue splittée. Point reporté « sans urgence » à la
livraison du journal d'audit (registre), rouvert ce
jour.feat/toasts, commit 2eab226) — validation visuelle en attente
toast-info et la pastille passent au bleu
#1a5fb4 ; le rouge #b3261e devient le défaut de la classe —
les avertissements (toast-warning) et toute sévérité imprévue restent traités en
alerte. L'icône ⓘ blanche existante convient au fond bleu, rien d'autre à toucher.inlineToast.ts vise désormais le pane focalisé,
en trois niveaux — injecteur (AppService.activeTab puis getFocusedTab()
récursif avec garde anti-cycle, élément par viewContainerEmbeddedRef ?? hostView →
rootNodes[0], testé isConnected), repli DOM
(split-tab > .child.focused, le plus profond), repli historique (l'onglet entier).
Point clé vérifié dans les sources de l'app installée : tout onglet terminal est déjà un
SplitTabComponent à un pane (openNewTab() enveloppe d'office),
donc un seul code couvre les deux cas, et le garde position === 'static' existant
suffit (un pane est déjà absolute — ne jamais forcer relative en dur)._allFocusMode) marque tous les panes
.focused : seul le chemin injecteur reste univoque, d'où son rang.Validé par l'utilisateur le 2026-08-14, après une passe en deux temps. La première (T1 KO deux fois, T2–T5 OK) a mené à quatre correctifs, le diagnostic décisif ayant été fait en direct par CDP + injecteur sur le poste réel plutôt que par hypothèses :
f947d4b — le chemin injecteur ne retient qu'un élément portant la classe
child (vrai pane, posé par splitTab.layout()) : l'hôte d'un onglet brut
ou d'un pane pré-layout est un conteneur défilant où bottom: 1rem tombe sous la
ligne de flottaison — pastille insérée mais invisible, annonce consommée.1da3752 — renonciation de 30 s supprimée : une session qui démarre sans onglet
(aucune restauration) dépassait la fenêtre le temps d'un détour par les réglages, et l'annonce
unique mourait en silence. Mesuré en direct : activeTab: null,
unlockAnnounced: true, aucune pastille.ebbda5d — repli en toast de fenêtre (demande utilisateur) :
course entre activeTabChange$ (→ pastille dans le terminal, le chemin voulu) et un
délai de 3 s (→ toastr.info ancré à la fenêtre, 10 s à l'écran). Une seule annonce
par session, quel que soit le canal.3984bbb — notifications signées : « Better Vault — … » sur la
pastille et le titre des trois toasts (déverrouillage, enregistrement, avertissement trousseau).
Nom propre non traduit, concaténé dans le code — aucune clé i18n nouvelle.Le commentaire de toast.scss le dit : « rouge et surdimensionné à la demande de
l'utilisateur » — l'enregistrement d'un mot de passe maître méritait d'être vu. La décision du
2026-08-14 ne le contredit qu'à moitié : la visibilité reste voulue, c'est la couleur
d'alerte qui ne doit plus être partagée par des messages de natures différentes. À noter en
codant : l'icône est un SVG recoloré en dur dans le CSS (impossible à teindre autrement), et
toastClass remplace la classe par défaut de ngx-toastr au lieu de s'y
ajouter.
Relevés par l'utilisateur dans son Project GitHub le 2026-08-10, rapatriés ici le 2026-08-14
après vérification sur pièces (bundle, node_modules, registre npm). Par ordre de
priorité :
chore/hygiene-paquet, commit 02e6c0a)
Les points 1 à 3 sont faits, le point 4 est traité pour sa part corrigeable :
webpack.config.js exporté en fonction
(un objet ne voit pas le --mode production que webpack-cli fusionne par-dessus —
c'est le mécanisme qui avait publié la map) ; devtool et pathinfo
coupés en production ; scripts/strip-source-maps.js (repris du projet frère) chaîné
dans build:prod. Prouvé : build de dev → map présente, puis
build:prod → strip-source-maps: 1 removed, et
npm pack --dry-run → 0 .map, 237 Ko décompressés contre 756 pour la
1.0.1 publiée. Le build de dev garde sa map — c'est lui que la jonction NTFS sert.THIRD-PARTY-NOTICES.md créé et ajouté à
files[] — js-yaml (texte MIT recopié intégralement : bundlé en entier, sa notice ne
survit pas à la minification), pug-runtime, runtimes css-loader et style-loader (ce
dernier trouvé au grep du bundle, absent d'index.js.LICENSE.txt), et l'attribution
du tracé Font Awesome info-circle du toast (CC BY 4.0, tracé seul, recoloré).CHANGELOG.md créé (format Keep a Changelog) —
Unreleased (ce lot) + 1.0.1 + 1.0.0, rétro-rempli depuis les releases GitHub et le
journal, liens de comparaison compris. Au dépôt seulement, comme sur la sidebar — pas dans
files[]. À tenir à chaque publication.npm audit fix nu appliqué — 19 → 16
(brace-expansion, fast-uri, nanoid corrigées) ; les 16
restantes sont celles bloquées par Angular 15 et pug 2, en surveillance comme prévu.Effectif sur npm depuis la 1.0.2 du 2026-08-14 (voir
Publication) — la 1.0.1 était la dernière à porter la map.
webpack-cli@4 laissé en l'état (hors lot, valeur faible).
Vérifié : devtool: 'source-map' est inconditionnel dans
webpack.config.js, même en production — dist/index.js.map (490 Ko) porte
l'intégralité des sources TS commentées, et files: ["dist"] l'embarque (la 1.0.1
publiée la contient). C'est le canal mesuré le 2026-08-02 en préparant la
campagne 4 : le code raconte ses propres correctifs. Dispositif de la
sidebar à répliquer tel quel : devtool à false en production
et scripts/strip-source-maps.js (34 lignes, sans dépendance — parcours de
dist/, suppression de tout .map) chaîné dans build:prod,
parce qu'un build de dev antérieur laisse sa map derrière lui et qu'output.clean
ramasserait les .d.ts écrits par TypeScript hors assets webpack.
THIRD-PARTY-NOTICES.md)La sidebar embarque le sien (files[]) depuis sa 1.0.1 ; ce paquet n'en a pas. Ce qui
est réellement distribué dans dist/index.js, vérifié dans le bundle :
js-yaml (importé par tabbyConfig.ts, inliné) et les runtimes
pug-runtime et css-loader (listés par index.js.LICENSE.txt).
ngx-toastr, lui, n'est pas distribué — external
(#V8), vérifié : le bundle n'en contient qu'un stub
module.exports = __WEBPACK_EXTERNAL_MODULE.... Le fichier doit couvrir ce qui est
distribué, pas les devDependencies. Format sidebar : prose par dépendance, licence recopiée
intégralement seulement quand l'adaptation du code l'exige.
CHANGELOG.md absentAucun fichier CHANGELOG.md dans le dépôt — mais l'état réel est nuancé : les
releases GitHub v1.0.0 (« First stable release ») et v1.0.1
(« Discoverability: keywords added »), créées le 2026-08-10 avec les tags, portent déjà des notes.
Ce qui manque est le fichier au dépôt, visible depuis npm et depuis un clone. Rétro-remplissage
faisable depuis les deux releases et AI-HISTORY.log, idéalement avec la prochaine
publication — la politique semver donne le découpage.
npm audit : 19 vulnérabilités, toutes hors livrable — surveillance, pas urgenceMesuré le 2026-08-14 : 19 vulnerabilities (1 low, 3 moderate, 15 high), toutes en
devDependencies — structurellement : package.json n'a aucun bloc
dependencies, et le seul artefact publié est dist/index.js. Répartition
(grille d'analyse reprise du projet frère, revérifiée ici à l'audit détaillé) :
@angular/*,
ng-bootstrap, @babel/core, et les pairs tabby-core /
tabby-settings marqués incorrigeables par npm. Correction = Angular récent, bloquée
tant que Tabby amont reste sur Angular 15 — et tout ce groupe est external : rien n'en voyage
dans le paquet.pug@2, build pur. La vraie sortie est pug 3 avec changement
de chaîne de loaders ; le « fix » que npm propose est un downgrade de
pug-loader (vers 1.0.2) — ne jamais --force.brace-expansion,
fast-uri, nanoid), corrigeables par un npm audit fix nu —
hygiène à faire à l'occasion.Différence avec le projet frère, vérifiée : aucune des 19 n'atteint le paquet
publié. La sidebar bundle dompurify (sa seule vulnérabilité embarquée, avec
un correctif dans sa plage) ; ici, ni js-yaml ni les runtimes inlinés
(pug-runtime, css-loader) ne figurent à l'audit. Pas d'urgence de
sécurité côté vault.
apply-loader, vérifié : toujours nécessaire — c'est lui qui exécute la
fonction template produite par pug-loader pour que
template: require('./x.pug') rende une chaîne — mais à l'abandon de fait (dernière
publication : 2.0.0, en 2017), écrit contre l'API options de webpack 1/2 ; il ne tient que parce
qu'aucune option ne lui est passée. S'en passer, c'est changer toute la chaîne pug, pas un simple
retrait — le moment venu, avec pug 3, sans urgence.
Le bump TS n'est pas « low-risk » : Angular 15 impose
typescript >=4.8.2 <5.0 (peerDependency de @angular/compiler-cli,
vérifiée dans node_modules) — le 4.9.5 actuel est le sommet de la plage, TS 5.x
impliquerait Angular 16+, donc une migration, pas un bump. webpack est déjà à jour
(5.109.2 = latest) ; seul webpack-cli (4.10.0, dernière de la branche 4) a trois
majeures de retard — bump possible à l'occasion, valeur faible, usage minimal
(--progress --color --mode --watch). L'audit le confirme : webpack-cli@4
ne tire aucune vulnérabilité — c'est de l'hygiène, pas de la sécurité.
Interface traduite (quatre langues), journal figé en anglais. Fait le 2026-08-01, vérifié sur Linux le 2026-08-01, macOS accepté comme non testable le 2026-08-06. Détail intégral, repris tel quel, dans le registre des livrés.
Exclusion de profils SSH du déverrouillage automatique (voie médiane : pop-up native pour le
profil exclu, pour cette connexion seulement), onglet de réglages dédié à groupes pliables, pont
BetterVaultExclusions:v1 pour la sidebar. Demandée le 2026-07-29, faisabilité vérifiée,
arbitrée, codée, testée et validée le 2026-08-07. Détail intégral, tel que livré, dans le
registre des livrés ; le défaut critique attrapé par
la première passe de test est le piège #V30.
Reste ouvert, côté sidebar uniquement : l'entrée du menu contextuel Profil/Groupe qui consommera le pont — chantier de l'autre dépôt, à son rythme.
Conçu, codé et validé le même jour (cinq vagues de tests, 25/25, y compris poids inversés — ce
plugin sait héberger la famille). L'état du chantier tel que livré vit au
registre des livrés ; la convention
(src/betterPanel.ts, contrat dupliqué dans chaque dépôt, jamais d'import npm entre
plugins) est documentée dans le CLAUDE.md des deux projets.
betterVault: enabled: true dans config.yaml — closRepéré le 2026-08-07 dans la config réelle de l'utilisateur (déchiffrée le temps d'une passe de
test) : une clé betterVault: y subsistait, alors que la règle #V11 interdit tout réglage
de ce plugin dans config.yaml — l'état vit dans better-vault.json.
Reliquat d'une version ancienne. Vérifié le jour même : aucun code de src/ n'y lisait ni
n'y écrivait. Retiré par l'utilisateur de sa propre config le 2026-08-07, geste
resté à sa main comme prévu, jamais fait d'office.
Les deux cas d'usage identifiés sont livrés, chacun sur le même pattern — clé de chaîne
via l'injecteur Angular partagé, contrat dupliqué dans chaque dépôt, jamais d'import npm entre
plugins : les onglets de réglages par le
panneau unifié Better Tabby
(BetterPanelContribution:<id>), la bascule d'exclusion par le pont
BetterVaultExclusions:v1 de la
granularité par profil
(src/exclusionsBridge.ts, qui porte le contrat commenté à dupliquer côté sidebar).
L'idée d'origine de cette section — un service importé en dépendance npm
optionnelle — est morte et enterrée : le chargeur de Tabby n'intercepte qu'une courte liste de
modules par nom, tout autre paquet tabby-* présent dans un node_modules de
plugin se charge en double (#V30, et #34 côté sidebar). La
clé de chaîne n'est pas un pis-aller, c'est la seule forme sûre. Seul reste ouvert, côté sidebar :
l'entrée de menu contextuel qui consommera le pont.
Deux revues successives (A-15, cas B) : celle du 2026-07-29, détaillée dans
AI-HISTORY (commits 47adf46 et a80430e), puis
celle du 2026-08-01, qui a mis le projet en conformité avec la charte refondue en
noyau / options / entretien de cadrage. Ce n'est pas un état acquis : la
charte évolue, ce projet doit suivre.
Elle rapatrie le REMISE-A-NIVEAU.md porté par la branche de propagation, supprimé
dans le même geste : deux emplacements pour le même fait divergent (A-2, A-7).
complet, vingt-deux options tranchées, aucune ligne « défaut
appliqué, non tranché ». Trois réponses changent une pratique plutôt que d'enregistrer
l'existant : branches = branche (le projet travaillait en direct sur
master), dépendances = minimales, et
attribution = co-auteur seul, sans lien de session.A-x — dans ce document,
dans README.html et dans CLAUDE.md. Pas dans
AI-HISTORY : le journal est en ajout seul (A-4), et c'est
exactement ce que la table de correspondance en fin de charte absorbe.#V19.html-validate en devDependency,
npm run lint:airules (A-14, option validateur)..AIRules/, LICENSE et README.md — quatre
jours après l'écriture du plugin complet. La revue du 2026-07-29 avait corrigé la même erreur dans
le README sans la voir dans AI-CONTEXT : corriger la page où la
dérive est constatée ne dit rien des autres pages.visibilité range explicitement dehors.20260804-071239Deux branches de propagation automatique (governance-sync-20260804-064238,
governance-sync-20260804-071239) attendaient depuis le 2026-08-04, déclenchées par
`GOUVERNANCE` en entretien de cadrage. Fusionnées l'une après l'autre sur master ;
la seconde entrait en conflit avec la première sur GOUVERNANCE-IA.md et
GABARITS.md (les deux branches partaient du même point, la première ne portant pas
la révision de la seconde) — résolu en retenant la version de la seconde, cumulative, **vérifiée
par empreinte Git** contre le dépôt canonique avant de committer.
Ce que les trois révisions apportent (20260803-200821, 20260804-064238,
20260804-071239) :
Règle N → A-x, devenue une
condition de retrait inatteignable côté charte, et ajout du prochain numéro d'invariant libre
(A-16).AI-HISTORY.html change un
jour de support.registre-livrés (A-8) : sépare, si activée, la roadmap active du
registre des chantiers déjà livrés.journal-format (tableau/log), qui
clôture la réforme d'amaigrissement de la roadmap du dépôt canonique.Les trois points ci-dessus sont réglés par l'entretien de cadrage relancé dans la foulée
(mot-clé GOUVERNANCE), détail dans PROFIL.md et
AI-HISTORY.html : la table Règle N → A-x
est recréée localement dans AI-CONTEXT.html pour les seules
références que porte ce journal ; registre-livrés est tranché à oui ;
journal-format est tranché hors du menu de la charte — un vrai
fichier AI-HISTORY.log plutôt que le tableau plat HTML que l'option décrit. Les deux
dernières sont des décisions de principe : leur migration reste à faire, voir
ci-dessous et ci-dessous.
Claude-Session de l'historique public — fait le
2026-08-01. Les 32 commits concernés ont été réécrits et republiés ; la table de
correspondance des hashes est en tête d'AI-HISTORY.
Réserve à connaître : une réécriture ne détruit pas les anciens objets côté
GitHub, qui restent atteignables par leur SHA complet tant qu'ils n'ont pas été ramassés — seule
une demande au support les purge vraiment. Le contenu retiré n'étant qu'une URL de session, la
demande n'a pas été faite ; à reconsidérer si une donnée réellement sensible devait un jour être
retirée de la même façon.RAPPORT-TEST-LINUX.md déplacé vers annexes/ — fait le
2026-08-01. Il vivait dans .tempfiles/, dossier que l'option
tempfiles déclare jetable par construction et dont les fichiers exploités
« se suppriment sans demander confirmation » : ses conclusions étant absorbées ici et dans le
README public, il était à un geste d'être supprimé à juste titre. Il n'était par
ailleurs atteignable par aucun lien — deux mentions textuelles seulement, donc
orphelin au sens d'A-8. Il porte désormais son lien de retour et est cité depuis la
campagne, dans le registre des livrés..tempfiles/ et
c'est délibéré : il porte les valeurs que l'opérateur doit saisir, qui n'ont pas leur place sur un
dépôt public. Conséquence assumée : il n'existe que sur le poste de l'auteur, et une perte
imposerait de le réécrire.AAAAMMJJ-HHMMSS, monotones : une comparaison de chaînes suffit. Si la première est
antérieure, signaler l'écart et proposer une remise à niveau — jamais l'appliquer
d'office.git hash-object) et non par diff nu — core.autocrlf fait
apparaître une divergence totale sur un contenu identique.Le marqueur « Conforme à la charte du … » a d'abord été posé sur un README.html
auquel il manquait les deux callouts obligatoires. C'est le pire cas de figure : une session
ultérieure compare cette date au lieu de relire la charte, donc un marqueur trop généreux
étouffe la détection même qu'il sert à déclencher. Ne l'avancer qu'après avoir vérifié les éléments
exigés, pas seulement la date.
Option registre-livrés tranchée à oui à l'entretien de cadrage du
2026-08-06 (PROFIL.md) : cette roadmap dépassait 1700 lignes, en grande
partie des chantiers déjà ✅ Livré/Adopté — exactement le cas que
l'option cible.
annexes/REALISE.html créé le 2026-08-06 (A-8, jamais
archive/, A-9). Premier lot : les trois campagnes de vérification Linux
(campagne indépendante,
deuxième,
troisième). Deuxième lot, le même jour, une fois
macOS accepté comme non testable (voir correctifs-c3) : ce chantier
devenait terminal au sens de l'option — « testé sur les plateformes visées », pas « aucune inconnue
nulle part » — et l'i18n avec lui, pour la même raison
(correctifs-c3,
i18n).
Troisième lot, le 2026-08-07 : Panneau de
réglages et Journal d'audit, tous deux livrés le
2026-07-28 et déjà marqués ✅ Validé en conditions réelles dans le tableau récapitulatif
— mais annoncés ici même comme « non terminaux », à tort. Vérification menée section par section :
#reglages ne portait aucun point « reste ouvert », contrairement à ce
que cette page affirmait ; #audit en portait un — la notification ancrée à l'onglet
plutôt qu'au panneau actif d'une vue splittée, 📋 reporté, sans
urgence — repris tel quel dans son entrée du registre, sur le même principe que la cinquième
langue restée ouverte dans l'entrée i18n : un point mineur et
non bloquant n'empêche pas un chantier d'être terminal. En revanche, sa section listait cinq points
comme « manquants » qui étaient en réalité livrés le jour même par 10ad983 — erreur de
documentation, pas un chantier resté ouvert ; corrigée en migrant, pas en la laissant sur place.
Sept entrées au total, chacune reprise verbatim avec son renvoi vers l'entrée de
journal qui l'a actée, ses évolutions post-livraison, ses pièges et ses commits. La roadmap active ne
garde plus qu'une ligne et un lien par chantier livré (voir le tableau plus haut), et
plus aucun candidat non terminal à ce jour. Un chantier Écarté ne
bascule jamais au registre, il reste « Hors périmètre » dans la roadmap active.
.log — fait le 2026-08-06, sans conversion rétroactiveOption journal-format tranchée le 2026-08-06 hors du menu de la
charte (PROFIL.md) : les nouvelles entrées vont désormais dans un
vrai fichier texte brut, AI-HISTORY.log, pas le tableau
plat HTML que l'option journal-format=log décrit. Colonnes retenues :
horodatage | CHANTIER | Commit/Hash | Explication, une ligne par entrée, Explication
bornée à 250 caractères.
Compresser les entrées existantes d'AI-HISTORY.html à 250 caractères aurait
contredit la condition de contenu constant qu'exige l'exception nommée d'A-4 pour
une conversion de format — plusieurs entrées dépassent 1000 caractères de récit qu'aucun résumé de
250 caractères ne peut porter sans perte. Décision retenue à l'entretien : « log » ne vaut
que pour l'avenir. AI-HISTORY.html est gelé tel quel (callout ajouté en tête),
AI-HISTORY.log démarre vide et reçoit tout ce qui se journalise à partir de cette
migration.
Fait : AI-HISTORY.log créé avec son en-tête de format ;
AI-HISTORY.html porte désormais un callout de gel avec renvoi ; la table
README.html § Structure documente les deux fichiers séparément.
Clos le 2026-08-14 : le validateur dédié existe — npm run
lint:history (tools/lint-history-log.js, local comme lint:i18n) :
quatre colonnes, horodatage ISO, slug, hash 7 hexadécimaux ou -, explication bornée à
250 caractères. Deux tolérances parce que le journal est en ajout seul : le dépassement de longueur
n'est une erreur que pour les entrées datées de sa naissance (2026-08-14) ou après —
l'existant figé est signalé sans faire échouer —, et l'ordre anté-chronologique n'est qu'un
avertissement. Sa première exécution a attrapé trois lignes du jour au-dessus de la borne,
raccourcies dans la foulée (A-5). La navbar HTML des
quatre documents principaux n'a pas changé : elle continue de pointer vers
AI-HISTORY.html, qui renvoie lui-même vers le .log — décision délibérée,
pour ne pas toucher un élément identique sur quatre pages (§ 1 de GABARITS.md) pour un
lien secondaire.