TL;DR
Un agent « Cloud » avait livré la chaîne agentique du Cockpit (vue #/workflow : sessions,
commandes, suivi documentaire) et l'avait fusionnée en amont. Sauf que le commit n'était pas
dans le clone local. Le vrai geste n'était donc pas d'écrire la fonctionnalité — elle existait —
mais de la rattraper, la vérifier et l'aligner. En trois mouvements rejouables :
- Récupérer le commit fusionné dans un worktree isolé, sans jamais muter le clone partagé ;
- Vérifier en local : 133 tests verts,
check:secretsvert, et quatre scénarios mock qui distinguent « liste vide confirmée », « source indisponible » et « donnée périmée » ; - Déployer sur le VPS — en découvrant qu'il n'est pas un clone git mais un dossier
synchronisé : le script de déploiement officiel (un
git pull) est donc inutilisable, on passe par un overlay de fichiers, après sauvegarde.
L'enseignement n'est pas la fonctionnalité. C'est où sont les pièges quand une commande locale pilote une machine distante.
1. Le contexte : un travail fusionné, absent du local
Le Cockpit est un panneau de commande lecture seule du Royaume VFG. Il a une sous-page « Chaîne
agentique & Vie » (#/workflow) qui devait gagner une lecture d'activité : sessions d'agents,
commandes de la Citadelle, et un suivi documentaire daté.
Ce lot avait été développé par un agent « Cloud », poussé, fusionné. Le problème est apparu en
ouvrant le clone local : il traînait deux commits en arrière. Aucun des fichiers du lot n'existait
localement — ni les fixtures (demo/chain-fixtures.mjs), ni la vue (public/js/agent-chain.js), ni
les lectures BFF (bff/lib/agent-sessions.mjs), ni les tests.
Autrement dit : la fonctionnalité était déjà écrite, mais invisible d'ici. La refaire aurait été le double du travail, et la source de deux implémentations divergentes.
2. Le rattrapage, geste par geste
A. Récupérer sans polluer le clone partagé
Le réflexe serait de faire un git pull dans le clone partagé. Mauvaise idée : ce clone est utilisé
par d'autres sessions. La règle (1 agent = 1 worktree exclusif) protège tout le monde : on lit le
clone, on travaille ailleurs.
# clone partagé = source de vérité, on ne le mute pas
cd D:\VFG_PT__\VFG_Cockpit
git fetch origin --prune # amène origin/main au commit de fusion
git worktree add D:\GitRepos\.worktrees\VFG_Cockpit-agent-chain `
-b chore/agent-chain-integration origin/main
cd D:\GitRepos\.worktrees\VFG_Cockpit-agent-chain
npm ci
Le worktree démarre sur bf67481 (« Merge PR #6 »). Le clone partagé, lui, n'a pas bougé d'un octet.
B. Vérifier localement, avant de croire le commit
Un commit fusionné n'est pas une preuve qu'il fonctionne. On exécute d'abord les tests du lot, puis la suite complète, puis le garde-fou des secrets :
node --test test/chain-fixtures.test.js test/agent-chain.test.js `
test/agent-sessions.test.js test/contrat-demo.test.js
npm test; npm run check:secrets
npm run cockpit:demo # artefact offline autoportant
Résultat mesuré : 29/29 sur les tests ciblés, 133/133 sur la suite entière, check:secrets
vert, et l'artefact offline généré (394 Ko, zéro réseau).
Ensuite, l'aperçu : le front se lance sur un port de dev. Attention — le port par défaut était déjà pris par le serveur d'une autre session ; on prend le suivant, et on ne tue pas le voisin.
$env:PORT='5232'; node scripts/dev-static.mjs
# ouvrir : http://127.0.0.1:5232/public/index.html?mock=1&chainScenario=observed#/workflow
Les quatre scénarios rendent bien leurs états distincts, avec un bandeau SIMULATION :
chainScenario | Ce que la vue affiche |
|---|---|
observed | six états de session + quatre commandes |
empty | « Liste vide : aucune session visible. » (collecte réussie) |
unavailable | « source indisponible — état inconnu » |
stale | « donnée périmée — activité actuelle non confirmée » |
| valeur invalide | retour au mock historique, sans bandeau |
Enfin la contre-épreuve qui compte : le BFF réel ne doit jamais servir de fixture. On relance le serveur de production local et on ouvre la même URL « piégée » :
npm run cockpit # :5230
# ouvrir http://127.0.0.1:5230/?chainScenario=observed#/workflow
# attendu : badge POLL (pas MOCK), PAS de bandeau SIMULATION
C'est le point qui distingue une démo d'un mensonge : chainScenario ne sélectionne des fixtures
que dans la source mock, jamais sur le BFF réel. Même en interrogeant ?chainScenario=observed,
l'API ne contient ni simulation, ni commande simulation-order.
C. Déployer sur le VPS — le piège central
Ici arrive le piège qui justifie tout ce texte. Le dépôt fournit un script de déploiement qui fait
un git pull sur le serveur :
# deploy/deploy-vps.sh --local : ce que le dépôt propose
cd /opt/vfg-cockpit
git fetch origin && git checkout main && git pull --ff-only origin main
Or, en mesurant — pas en supposant — le dossier de prod n'est pas un dépôt git : il n'a pas
de .git, juste un fichier d'état avec un SHA. Le script officiel échouerait. Le mécanisme réel,
c'est un sync de fichiers, hérité d'une installation par archive.
On construit donc une archive du HEAD (fichiers suivis uniquement, donc sans .env ni
node_modules) et on la pousse :
$sha = (git rev-parse HEAD).Trim()
git archive --format=tar.gz -o "$env:TEMP\vfg-cockpit-$sha.tgz" HEAD
scp -F NUL -i $HOME\.ssh\id_ed25519 "$env:TEMP\vfg-cockpit-$sha.tgz" debian@51.77.244.222:/tmp/
Puis, côté serveur : sauvegarder d'abord, overlay ensuite, redémarrer, vérifier.
tar -czf /home/debian/vfg-cockpit-backup-$(date +%Y%m%d-%H%M%S).tgz \
-C /opt/vfg-cockpit --exclude=node_modules --exclude=.env .
tar -xzf /tmp/vfg-cockpit-<sha>.tgz -C /opt/vfg-cockpit
printf '%s\n' <sha> > /opt/vfg-cockpit/.deployed-sha
sudo systemctl restart vfg-cockpit
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5230/api/etat # 200
Contrôles finaux : service active, /api/etat 200, les nouveaux assets servis en 200, la
charge utile contient de vraies sources (chronos:/v1/query/agents/active,
citadelle:/api/etat#orders) et aucune marque de fixture. On vérifie aussi que l'overlay n'a pas
écrasé une config propre au serveur : seul l'ajout attendu (l'entrée chronos) diffère.
3. Les pièges à retenir
Mesurés ce jour-là, pas devinés :
- PowerShell 5.1 mutile les commandes distantes. Un
|, un<, des guillemets ou unnode -e "…keys…"dans une lignesshcassent le parseur côté Windows. Le remède fiable : écrire un petit script.shet le pousser viaGet-Content x.sh -Raw | ssh host '…', puis le supprimer — sinon le worktree reste sale. <est interdit dans PowerShell, et uncdpeut se perdre selon le mode d'exécution. On garantit le dossier avecPush-Location.- Le port de dev était déjà pris par une autre session. On ne tue pas le voisin : on prend le port suivant.
.deployed-sha≠ clone git. Le déploiement de ce service est un sync de fichiers.- Overlay ≠ suppression. Les fichiers de sauvegarde restés en prod sont inoffensifs ; on distingue « ce qui manque » de « ce qui est en trop » par un diff de listes, pas à l'œil.
- Les fichiers générés (l'artefact offline, ignoré par git) ne sont pas dans l'archive — sans conséquence : ils ne sont pas servis en prod.
4. État final et points ouverts
- Rollback prêt : une archive de sauvegarde du dossier prod, horodatée, existe sur le serveur.
- Serveurs locaux arrêtés, worktree propre, clone partagé intact.
- Une trouvaille honnête : l'amont qui alimente les sessions (le service Chronos, sur le port
4344) ne répond pas sur le VPS — les services présents écoutent sur4340-4342. La vue affiche donc, correctement, « source indisponible — état inconnu ». Le correctif probable est de pointer la configuration sur le bon port — à confirmer avant d'agir. - Deux décisions restent humaines : pousser la branche d'intégration, et corriger l'adresse de Chronos.
Rien de tout cela n'est spectaculaire. C'est justement l'intérêt : un travail d'agent livré ailleurs ne se rattrape pas en le réécrivant, mais en le récupérant proprement, en le vérifiant, et en sachant où la maison réelle ne ressemble pas au plan.