Roadmap

Dernière revue : 2026-08-14

Vue d'ensemble

ChantierStatutPriorité
Vérification de faisabilité (VaultService) 📋 Terminée — API absente, contournement validéAbsolue
État de l'art (plugins existants) 📋 Fait le 2026-07-28Cadrage
Contournement : patch de getPassphrase 📋 Mécanisme tranché — acté par l'utilisateur le 2026-07-28Fondation
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-28Cœ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 ouverteEntretien 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

Vérification de faisabilité — résultat (2026-07-28)

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-fouRé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).
Verdict : le garde-fou n'est pas franchi

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.

État de l'art — ce qui existe déjà (2026-07-28)

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.

PluginSujetRapport à 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.
Positionnement retenu

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é.

Ce que le code de tabby-vault-keepassxc nous apprend

Son 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.

Contournement retenu — patch de getPassphrase

Décision actée le 2026-07-28 par l'utilisateur

Question 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é.

Principe

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.

Correction d'une erreur d'analyse du 2026-07-28 (matin)

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).

Conséquences connues et fragilités

Spike d'observation — verdict rendu le 2026-07-28

✅ Les deux inconnues sont levées — le projet est viable
InconnueVerdict
#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.

Pourquoi observer avant de substituer

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.

  1. Lève #V4 (timing) : journaliser l'horodatage de chargement du module du plugin, celui du premier appel à 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.
  2. Lève #V7 (accès à l'OS) : tester require('@electron/remote') puis safeStorage.isEncryptionAvailable() depuis le plugin, et journaliser le résultat sans rien chiffrer.
  3. Vérifier au passage que injector.get(VaultService) rend bien l'instance déjà construite et que notre enveloppe se pose au-dessus de celle de serializeFunction.

Risque de faisabilité restant — le timing (non tranché)

À vérifier empiriquement avant de s'engager

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.

Point ouvert secondaire — accès à safeStorage

Largement 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.

Scaffold du plugin

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) :

Le reste du scaffold est explicitement gelé tant que la décision sur le contournement n'est pas prise.

Déverrouillage automatique du coffre-fort

Design initial invalidé le 2026-07-28 — conservé pour référence

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.

Délégation à l'OS via electron.safeStorage

Flux d'injection

  1. Mécanisme opt-in (à activer explicitement dans les options du plugin — jamais actif par défaut sans action explicite de l'utilisateur).
  2. Au démarrage, déchiffrement du token via l'OS puis tentative d'injection programmatique dans le VaultService de Tabby, avant que la pop-up native de saisie n'apparaisse. Étape caduque : l'injection programmatique n'existe pas (#V2), et le moment d'apparition de la pop-up dépend de config.encrypted (#V4) — elle n'apparaît pas systématiquement au démarrage. À remplacer par le contournement.
  3. Garde-fou si l'injection échoue (ex. mot de passe maître changé depuis les réglages natifs de Tabby) : suppression immédiate du token local obsolète, retour transparent à la pop-up native — jamais d'erreur bloquante affichée à l'utilisateur pour cette fonctionnalité de confort.
Sécurité RAM — non négociable, reformulé le 2026-07-28

É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).

Stockage du jeton local — décidé le 2026-07-28

Fichier séparé : better-vault-token.bin, à côté de config.yaml dans le dossier de configuration de Tabby. Jamais dans config.yaml.

La raison n'est pas celle qu'on croit

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 :

  1. La machine A enregistre son jeton → synchronisation.
  2. La machine B le reçoit, écrase son config.yaml, échoue à le déchiffrer, le purge (garde-fou normal), sauvegarde → resynchronise.
  3. La machine A retélécharge : son jeton a disparu, l'utilisateur doit ressaisir.

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).

Garde-fou Linux — 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.

Correction du 2026-07-29 — le bureau n'est pas le déclencheur

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.

Exercé en vrai le 2026-07-29 — dans les deux sens

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.

Panneau de réglages et politique d'expiration — livré le 2026-07-28

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.

Journal d'audit et notifications — livré le 2026-07-28

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.

Publication

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).

Publiée le 2026-08-14 — tabby-better-vault@1.0.2 : hygiène du paquet et notifications

Publication 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.

Publiée le 2026-08-10 — 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.

Publiée le 2026-08-07 — tabby-better-vault@1.0.0 sur npm

Publication 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.

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.

Dépôt public nettoyé avant publication (2026-08-07)

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.

Politique de version — décidée le 2026-08-06

Semver standard, appliqué explicitement à partir de 1.0.0 :

À trancher avant publication

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.

Campagnes de vérification Linux — livrées

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.

Correctifs issus de la 3ᵉ campagne — livrés

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.

Quatrième campagne — préparée le 2026-08-02, non menée telle quelle

Ce qui s'est réellement passé le 2026-08-05

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.

Trois postes en parallèle, un lot chacun

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.

Ce que la préparation a changé au protocole, et pourquoi

Retirer .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 format d'installation était en contradiction avec le protocole

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.

Les sollicitations de l'opérateur sont la ressource rare

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é.

Ce qui reste acquis des campagnes précédentes

Journal saturé par les déverrouillages répétés — constat du 2026-08-03

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.

Le pont ne boucle pas — c'est Tabby qui appelle, et nous qui parlons

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.

Ce que chaque appel coûte réellement

Ce qui n'est pas en cause

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.

Ce qui est retenu

  1. Étouffer la répétition dans 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().
  2. Capturer la pile en mode observation (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.
Mécanisme de temporisation validé à l'exhaustif en isolation (2026-08-06), confirmé en usage réel le 2026-08-07

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.

Ce qu'il ne faut pas faire — la fausse bonne idée

É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.

Notifications : ancrage au panneau actif et couleurs par sévérité — ouvert le 2026-08-14

Deux axes, décidés ensemble par l'utilisateur le 2026-08-14 :

Implémenté le 2026-08-14 (branche feat/toasts, commit 2eab226) — validation visuelle en attente

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 :

Le rouge n'est pas un accident — c'est une décision qui change

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.

Hygiène du paquet et de l'outillage — cinq points relevés le 2026-08-10

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é :

Lot livré le 2026-08-14 (branche chore/hygiene-paquet, commit 02e6c0a)

Les points 1 à 3 sont faits, le point 4 est traité pour sa part corrigeable :

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).

1. La source map part dans le tarball npm — à corriger avant toute republication

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.

2. Notices tierces absentes (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.

3. CHANGELOG.md absent

Aucun 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.

4. npm audit : 19 vulnérabilités, toutes hors livrable — surveillance, pas urgence

Mesuré 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é) :

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.

5. Bump TypeScript / webpack-cli — la note d'origine est à corriger

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é.

Internationalisation — livrée

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.

Granularité par profil — livrée

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.

Panneau de réglages unifié « Better Tabby » — livré le 2026-08-07

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.

Reliquat betterVault: enabled: true dans config.yaml — clos

Repé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.

Interfaçage avec tabby-better-sidebar — clos

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.

Conformité de la gouvernance à la charte

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.

Revue du 2026-08-01 — ce qu'elle a traité

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).

Dérives constatées le 2026-08-01

Revue du 2026-08-06 — fusion de la charte 20260804-071239

Deux 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) :

Ce que l'entretien du même jour a tranché

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 NA-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.

Ce qui reste ouvert

Entretien récurrent

Une mention de conformité vaut promesse

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.

Registre des livrés — sept entrées migrées, la dernière paire le 2026-08-07

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.

Journal en .log — fait le 2026-08-06, sans conversion rétroactive

Option 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.

Pas de conversion rétroactive — un conflit trouvé en préparant la migration

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.