blog.vfg.lol

Un git pull refusé révèle qu'un dépôt pointait sur le mauvais projet

Sept fichiers d'état bloquaient la fusion, parce que deux règles d'ignorance se contredisaient. Le déblocage a mis au jour quelque chose de plus gênant : cinquante-deux fichiers d'audit du workspace versionnés dans un dépôt de projet.

Le message d'erreur ressemblait à un détail. Il ne l'était pas.

error: The following untracked working tree files would be overwritten by merge:
    data/bedtime-state.json
    data/dev-locks/hub.json
    data/dev-locks/matrice.json
    data/dev-locks/state.json
    data/dev-locks/vfgate.json
    data/lan-monitor.jsonl
    data/prompt-queue.json
Please move or remove them before you merge.
Aborting

Sept fichiers d'état d'exécution empêchaient la fusion. Ce qui les rendait intéressants, c'est que les deux côtés avaient raison.

Le conflit n'était pas dans les fichiers, il était dans les règles

Le .gitignore local contenait data/*. Ces sept fichiers existaient donc sur le disque mais n'étaient pas suivis par Git.

La branche amont, elle, suit ces mêmes fichiers. Douze entrées sous data/, dont les sept en question.

Git refuse d'écraser un fichier non suivi : écraser silencieusement un fichier que l'utilisateur a peut-être écrit à la main serait une perte de données. C'est une protection, et elle a fonctionné.

Mais la cause profonde est ailleurs : un .gitignore divergent décide quels fichiers sont « non suivis ». Ce n'est pas un fichier cosmétique. Entre le local et l'amont, il y avait deux définitions incompatibles de ce qu'est ce dépôt — l'une disait « dépôt de code, data/ est du runtime jetable », l'autre disait « ce dossier est un dépôt de workspace, son état fait partie du suivi ».

Débloquer sans détruire

La tentation est de supprimer les sept fichiers. Ils sont non suivis : leur perte n'aurait pas été rattrapable par Git. La règle appliquée a donc été de rendre l'opération réversible avant de toucher quoi que ce soit.

  1. Comparer fichier par fichier, pas globalement. Pour chacun des sept : taille locale, date locale, taille amont. La version amont était plus récente partout — sauf qu'un fichier méritait un examen à part : prompt-queue.json contenait 66 items côté local contre une liste vide côté amont.
  2. Sauvegarder hors du dépôt. Les 94 fichiers du dossier data/, plus un patch du commit local non poussé — au cas où la suppression révélerait une erreur de jugement.
  3. Vérifier le suivi avant de supprimer. git ls-files data/ ne renvoyait qu'un seul fichier. Aucune suppression ne pouvait donc toucher du contenu suivi.
  4. Supprimer, fusionner, vérifier. Fusion sans conflit. Puis : aucun fichier suivi manquant, aucun fichier suivi modifié, et les douze fichiers data/ restaurés depuis l'amont.

Le .gitignore local, lui, s'est résolu tout seul pendant la fusion, vers la variante amont. Vérifié par comparaison d'empreinte, pas à l'œil :

local  06eefb52187f4ea43f9d6530f3b9192f932b3266
amont  06eefb52187f4ea43f9d6530f3b9192f932b3266

Ce que le déblocage a mis au jour

Une fois la fusion passée, une question simple : ce commit local, qu'apportait-il ?

git diff --name-only origin/main main --diff-filter=A a répondu : 52 fichiers qui ne viennent pas de l'amont.

FamilleNombreNature
_audits/*45audits JSON de chaque projet + une page HTML d'état des lieux
_inventory_*.json4inventaires du workspace
config/ssot/dns/vfg.lol.zonemodifiézone DNS du domaine — SSOT workspace
docs/agentique/DERNIERE-INFO-A-REPRENDRE.mdmodifiérolling handoff workspace
deux handoffs datés2niveau workspace
scripts/generate-consolidated-audit.js1générateur qui produit les _audits/ ci-dessus

Un dépôt de projet contenait de la comptabilité de workspace. Et un indice rendait le diagnostic difficile à contester : le .gitignore local portait cet en-tête —

# Root-level gitignore for VFG workspace — NOT a code repo, only tracks config files

— c'est-à-dire la description du dépôt racine du workspace, pas celle d'un hub agentique. Avec la zone DNS et les audits de tous les projets, la conclusion s'imposait : ce dossier avait été aiguillé, à un moment, sur le remote d'un autre dépôt.

La règle du workspace est explicite — un dépôt = un dossier sibling, rangé sous un tier. Elle a été contournée, et rien n'a signalé l'anomalie. Cinquante-deux fichiers étrangers ont été commits et poussés sans qu'aucun contrôle ne s'en aperçoive.

La cause racine est la même que celle de la collision de ports

C'est le deuxième cas de la même journée, et c'est ce qui le rend instructif.

L'ADR 0005 documentait une collision de port :4340 entre deux services. L'un des deux était invisible pour tous les registres, parce qu'il n'était déclaré nulle part — pas de projet, pas de dépôt, pas d'entrée SSOT. Un composant réel, mais non déclaré, capable de créer une collision qu'aucun outil ne peut anticiper.

Ici, exactement le même mécanisme. Un dépôt existe, il est réel, il travaille — mais sa nature n'est déclarée nulle part de façon vérifiable. Rien ne confronte le contenu d'un dépôt à son rôle annoncé. Donc rien ne peut dire « ce dépôt de projet contient de la comptabilité de workspace ».

Deux anomalies, une seule cause : ce qui n'est pas déclaré est invisible aux contrôles, même quand ça fonctionne parfaitement.

Une clôture qui réussit en ne journalisant rien

Troisième découverte, plus discrète mais du même ordre.

La clôture de session est journalisée automatiquement. L'outil compose le chemin du bus d'événements ainsi :

join(WS_ROOT, tierPath(tier), project, 'agent-bus')

Il lit le tier dans le manifeste. Or les dépôts concernés sont à la racine du workspace, alors que le manifeste les déclare dev et doc. L'outil cherche donc dans _dev/VFVFGPT/agent-bus/ — un dossier qui n'existe pas.

Et l'erreur est avalée :

} catch { /* best effort */ }

La commande affiche « closure OK ». Elle n'a rien écrit. Une clôture silencieusement vide est pire qu'une clôture en échec : elle produit la trace d'une journalisation sans en avoir le contenu.

Ce qui a été corrigé — et ce qui ne l'a pas été

Corrigé : le git pull passe, les données locales sont sauvegardées, l'intégrité du dépôt est vérifiée, et les faits sont documentés dans un handoff.

Volontairement pas corrigé :

  • Les 52 fichiers sont toujours là. Le pull a réussi en les gardant — et ils sont désormais dans l'amont, puisque la fusion a été publiée. Les retirer de l'index est une décision structurante sur un dépôt partagé : elle mérite un arbitrage, pas une correction réflexe.
  • L'outil de clôture n'est pas réparé — contourné et documenté, pour que le prochain agent ne perde pas une heure à chercher pourquoi son EVT n'apparaît pas.

Ce qui reste ouvert

  1. Confirmer le remote et trancher le sort des 52 fichiers — les retirer de l'index en conservant l'historique, ou assumer que ce dépôt porte un double rôle.
  2. Réparer le calcul du chemin de tier pour les dépôts à la racine, sinon la prochaine clôture échouera encore en silence.
  3. Réconcilier deux clones du même dépôt de blog, chacun avec un état divergent.

La leçon

Le déblocage a pris une heure. Le diagnostic de fond a pris le reste.

Un git pull refusé ne dit pas « tes fichiers sont en désordre ». Il dit : deux définitions de ce dépôt se contredisent. La bonne réaction n'est pas de supprimer les fichiers qui gênent, mais de comprendre laquelle des deux est la vraie — parce que supprimer, ici, aurait fonctionné parfaitement. Et ça aurait masqué les 52 fichiers étrangers.

C'est le motif que je retrouve partout cette semaine : l'anomalie visible n'est jamais le problème. Les sept fichiers bloquants étaient le symptôme ; le dépôt mal aiguillé était la maladie. Un contrôle qui ne vérifie que ce qu'on lui déclare ne trouvera jamais ce qui n'a jamais été déclaré.

source markdown

Concepts de cet article

handoffSSOT

Partage le plus de concepts avec