Skip to content

feat: mettre a jour un poste depuis l'ecran d'administration (ADR-040) - #14

Merged
lostmind84 merged 21 commits into
mainfrom
design/mise-a-jour-depuis-admin
Jul 29, 2026
Merged

lostmind84 merged 21 commits into
mainfrom
design/mise-a-jour-depuis-admin

Conversation

@lostmind84

Copy link
Copy Markdown
Owner

Mettre à jour un poste demandait qu'un humain télécharge une archive, la décompresse, ouvre une console en administrateur et lance update.ps1. Aucun bénévole ne fera ça — et le poste reste sur la version qu'il avait le jour de son installation.

Cette branche livre le bouton. ADR-040, docs/02-architecture.md §15.5.

Ce qui a rendu la chose possible

Le service tourne en LocalSystem (internal/platform/service_windows.go n'écrit aucun ServiceStartName) : aucune élévation à obtenir. Et l'écran est dans le binaire (//go:embed all:dist) : un seul artefact à remplacer.

Go orchestre, update.ps1 bascule. Le script fait déjà l'arrêt, la sauvegarde horodatée, la copie, le redémarrage, /healthz et le retour arrière, et deploy/deploy_test.go le couvre — une seconde implémentation aurait divergé.

Trois défauts existants, payés par ce chantier

  1. L'écran client restait noir après toute mise à jour, et après tout installeur rejoué sur un poste qui marche — le geste que TROUBLESHOOTING.md recommande. Stop-OpenScaleBinaryHolders termine la tâche du kiosque, openscale-kiosk.xml n'a qu'un LogonTrigger, et personne ne la relançait. Le défaut a survécu parce qu'un humain qui met à jour finit par redémarrer le poste.
  2. update.ps1 confondait ses échecs sous un seul exit 1, alors que « restauré, le poste marche » n'appelle personne et « restauré, le poste est mort » demande quelqu'un tout de suite.
  3. Le nil typé. Sur un binaire dev, aucun service n'est construit ; affecter ce nil à une interface produit une interface qui n'est pas nulle, la garde répond faux, et le sondage du tableau de bord faisait tomber la connexion HTTP toutes les trois secondes. C'était le poste de chaque développeur.

Quatre décisions prises par des tests, contre le plan

  • tokens.test n'inventorie que --ink et --ink-muted comme texte → la pastille est soulignée, pas colorée.
  • admin-two-levels tient la page bénévole à une seule route → la disponibilité voyage dans /admin/api/health, pas dans un second appel.
  • filepath.Clean transforme sur Windows /etc/cron.d/evil en \etc\cron.d\evil, que filepath.IsAbs déclare relatif → le jugement des chemins d'archive se fait en séparateurs / avant conversion.
  • Le worker de sondage enregistre ses minuteries dans la goroutine appelante, comme le Hub et le superviseur. Trente jours passent en 0,28 s.

Ce que le banc a mesuré

CREATE_NO_WINDOW et jamais DETACHED_PROCESS : powershell.exe est une application console, et sans console à attacher son hôte abandonne — sortie en 100 ms, code 0, script jamais lu. Le drapeau dont le nom dit « détaché » est celui qui ne lance rien. Compte rendu : docs/superpowers/plans/2026-07-29-banc-detached-process.md.

Modèle de menace, assumé et écrit

L'empreinte vient de la même publication que l'archive : elle prouve que le téléchargement n'a pas été abîmé, pas que la publication est légitime. Ce qui change n'est pas le risque — déjà pris par tout bénévole qui suit la notice — mais le délai : une journée au lieu de plusieurs mois. Ce qui le contient : le contrôle 48 (owner/repo, jamais une URL, l'hôte étant compilé), le mot de passe à l'enregistrement, la journalisation en warn de tout changement de dépôt, et sa présence dans l'empreinte de configuration.

Vérification

Suite Go complète verte en -race, 686 tests front, boundary et deps verts, les trois cibles compilent, budget de l'écran client inchangé à 70,3 %.

Ce qui n'a pas été vérifié

Aucune bascule complète n'a eu lieu. Personne n'a vu update.ps1 écrire un outcome.json, ni le poste le relire au démarrage suivant, ni l'écran client revenir. Le banc a validé la survie du processus détaché, pas la boucle. Il faut deux publications issues de cette branche — le mécanisme doit être dans la version installée, et le update.ps1 neuf dans l'archive téléchargée, puisque aucun poste n'en porte un.

lostmind84 and others added 21 commits July 29, 2026 10:24
Conception validee : un bouton de l'ecran d'administration telecharge la version
publiee sur GitHub, verifie son empreinte, et laisse update.ps1 faire la bascule
avec son retour arriere. Le service tourne deja en LocalSystem : il n'y a pas
d'elevation a obtenir.

Deux defauts existants sont nommes au passage. La tache du kiosque n'est relancee
par personne apres schtasks /end - ni install.ps1 ni update.ps1 - donc l'ecran
client reste noir jusqu'a une ouverture de session ; cela ne se voyait pas parce
qu'un humain qui met a jour finit par redemarrer le poste. Et update.ps1 ne
distingue pas "echoue, restaure, poste sain" de "echoue, poste mort", alors que
les deux ne demandent pas la meme chose a un benevole.

Le depot suivi devient un reglage - le code est sous AGPL, une coop doit pouvoir
suivre son fork - mais en couple owner/repo et non en URL : un champ acceptant
une URL entiere ferait de "enregistrer la configuration" un "executer du code de
n'importe ou en LocalSystem".
Treize taches, en TDD, chacune avec son cycle de test et son commit.

La tache 0 est une MESURE et non du code : la PowerShell detachee survit-elle a
l'arret du service par le SCM ? Si non, les taches 6 et 7 changent de forme et la
bascule passe en Go. Vingt minutes sur le banc, avant d'ecrire quoi que ce soit.

Deux ecarts assumes par rapport a la spec, a y reporter en tache 13 : ERR-UPD-09,
parce que "la version a change depuis l'affichage" et "le poste est occupe" ne
demandent pas la meme chose a un benevole ; et Status.Supported cable a
runtime.GOOS plutot que deduit en appelant ApplyUpdate, ce qui lancerait une
PowerShell pour repondre a une question.
Verdict : oui, mais pas avec les drapeaux ecrits dans le plan. Le temoin lance
par le service a ecrit 113 lignes apres la mort de son parent et atteint ses
120 lignes, une fois DETACHED_PROCESS remplace par CREATE_NO_WINDOW.

Avec DETACHED_PROCESS, powershell.exe sort en 100 ms avec le code 0 sans lire
son script : c'est une application console, et son hote abandonne quand il n'a
aucune console a attacher. Mesure des quatre jeux de drapeaux dans le compte
rendu. L'approche A tient donc, et les taches 6 et 7 s'executent telles quelles
a cette ligne pres.

Deux trouvailles incidentes y sont consignees : -InstallDir et -DataRoot sont
des parametres morts sur install.ps1 et uninstall.ps1, ecrases par le
dot-source de common.ps1 juste apres leur liaison ; et un poste dont la
configuration porte des fautes bascule sur le profil neutre, donc sur le port
8085, quelle que soit l'adresse ecrite dans le fichier refuse.

Claude-Session: https://claude.ai/code/session_01DMgipLsTaTUZSsAGQLRDa1
Le plan de mise a jour depuis l'ecran porte desormais le verdict de sa tache 0
et la correction qu'il impose : CREATE_NO_WINDOW remplace DETACHED_PROCESS a
l'endroit meme ou le plan l'ecrivait, et la note pour l'implementeur interdit
de le retablir -- c'est le drapeau dont le nom dit "detache", et c'est celui
qui ne lance rien.

Une consequence du mode de defaillance mesure est ecrite la ou elle sera lue :
un Start() qui rend nil sans rien lancer laisse pending.json ecrit et aucun
outcome.json a venir, ClearPending ne tournant qu'a la lecture d'un compte
rendu -- le poste garde un pending.json eternel et refuse toute mise a jour
ulterieure par ErrAlreadyRunning. A borner avant d'ecrire les taches 5 et 7.

SUIVI.md prend le verdict, l'entree de journal, et le cinquieme point de ce que
faire tourner le poste a revele : -InstallDir et -DataRoot sont des parametres
morts sur install.ps1 comme sur uninstall.ps1, ecrases par le dot-source de
common.ps1 juste apres leur liaison. Le --listen ignore sur configuration
fautive, deja consigne en L8 et laisse ouvert, dit maintenant ce qu'il a coute
la seconde fois.

Claude-Session: https://claude.ai/code/session_01DMgipLsTaTUZSsAGQLRDa1
Le paquet internal/update commence par ce dont tout le reste depend : est-ce
que ce qui est publie est plus recent que ce qui tourne ?

Deux pieges nommes par des tests plutot que par des commentaires. Un depot
porte des tags de travail -- banc-de-test, avant-migration -- a cote de ses
versions, et un binaire compile hors tag s'appelle "dev" : les lire comme des
numeros offrirait une mise a jour vers rien, ou declarerait perime un poste qui
ne l'est pas. Et une preversion se classe SOUS sa propre version, sans quoi un
poste qui vient de quitter 2.1.0-rc1 se verrait proposer d'y revenir.
GitHubSource lit /releases/latest et non /releases : ce point d'entree exclut
deja les brouillons et les preversions, c'est son contrat, et s'y appuyer evite
au poste de trier une liste qu'il n'a pas construite.

Le test est ecrit contre une reponse REELLE de l'API, versee en testdata, pour
la raison qui a fait exister le corpus de trames : la donnee fait autorite
contre la documentation. Il verifie au passage que l'adresse lue est bien
browser_download_url et non le champ url, qui rendrait du JSON decrivant
l'archive plutot que l'archive.

Trois refus qui ne sont pas le meme. Un 404 dit qu'un depot n'a rien publie de
stable : ce n'est pas une panne, et un fork qui ne sort que des candidates est
dans ce cas. Un 403 de limite de debit et une page HTML de proxy sont
ErrUnreachable -- les lire comme "aucune version" annoncerait a un poste qu'il
est a jour alors que personne n'en sait rien.
Le Stager telecharge, compare a l'empreinte publiee, puis extrait. Rien n'est
garde sur un refus : un staging a moitie extrait ou non verifie serait repris
par une passe suivante comme s'il etait sain, et c'est la seule chose que cette
fonction existe pour empecher.

L'empreinte est calculee EN ECRIVANT et jamais sur un tampon -- l'archive fait
des dizaines de mega-octets. Et elle ne prouve que l'integrite du
telechargement : un fork qui aurait reorganise son archive la passerait, d'ou
le controle que openscale.exe et update.ps1 sont bien la.

Le jugement des chemins de l'archive se fait en separateurs "/" AVANT toute
conversion, et un test l'a impose. filepath.Clean transforme sur Windows
"/etc/cron.d/evil" en "\etc\cron.d\evil", que filepath.IsAbs declare RELATIF
puisqu'un chemin absolu Windows demande une lettre de lecteur : la premiere
ecriture du controle laissait donc passer un chemin absolu a la Unix. Le
symetrique est vrai sur Linux avec "..\..\evil.exe". L'archive vient du reseau
et c'est un processus LocalSystem qui l'extrait.
Le code est sous AGPL : une cooperative qui fait tourner son fork doit pouvoir
le suivre. D'ou un bloc update, et le controle 48.

Ce que le fichier nomme est un couple owner/repo et JAMAIS une URL. L'hote est
compile dans le binaire. Un champ acceptant une adresse entiere ferait de
"enregistrer la configuration" un "telecharger du code de n'importe ou, et
l'executer en LocalSystem" -- et ecrire ce fichier est exactement ce que
l'ecran d'administration existe pour faire. C'est le seul champ du fichier qui
designe d'ou viendra du code privilegie.

Le bloc entre dans Export(false), donc dans l'empreinte : quatre postes d'une
meme coop doivent suivre le meme depot, et celui qui diverge se voit sur les
huit caracteres qu'un benevole compare a l'oeil.

Deux pieges evites, tous deux deja payes par ce projet. L'absence de la cle est
LEGALE et vaut le defaut : c'est le symetrique du 28/07, ou le controle 20 a
fait refuser au poste sa propre configuration livree. Et le PROFIL NEUTRE nomme
le depot explicitement -- il est construit en Go, le defaut du decodage ne s'y
applique pas, et un poste hors service aurait echoue au controle 48 sur un
champ que personne n'a rempli. Or c'est precisement le poste qui doit pouvoir
se mettre a jour : le garde-fou le laissera passer, et un profil neutre invalide
aurait ferme cette porte-la.
UpdateGuard repond a une question, et la couche HTTP la POSE au lieu de lire un
etat pour en deduire une regle -- sans quoi la regle existerait a deux endroits.

La table couvre les seize etats de la machine, pour qu'un etat ajoute plus tard
ne tombe pas en silence du cote "oui, arretez le poste". Un test croise la regle
avec swapIsSafeIn : ce qui est trop occupe pour un basculement de catalogue doit
l'etre pour un redemarrage. La reciproque est fausse et c'est voulu --
OutOfService et Faulted refusent le basculement et autorisent la mise a jour,
parce qu'un poste qui ne peut pas servir est justement celui qui a besoin d'un
binaire neuf. C'est la porte que le profil neutre garde ouverte en nommant son
depot.

Le catalogue en attente refuse, et c'est la clause qui compte le plus. Un
pendingBatch signifie que le CSV a deja ete lu ET SUPPRIME -- la suppression est
l'acquittement -- et que les produits ne vivent plus que dans la memoire de ce
processus. Arreter le poste la ne differe pas le catalogue : il le PERD, et rien
ne le proposera jamais plus. C'est le mode de defaillance deja paye une fois
avec le premier catalogue d'un poste sans balance. Le miroir atomique existe
pour cela : pendingBatch appartient a la goroutine de boucle, et le lire depuis
un gestionnaire HTTP serait une course.
Rien ici ne vaut une migration de schema, et surtout : un poste qui ne demarre
pas doit pouvoir dire ce qui lui est arrive, ce qui est exactement le moment ou
une base de donnees est la chose sur laquelle on ne peut pas compter.

TakeOutcome consomme le compte rendu une seule fois -- le renommage est ce qui
rend l'operation idempotente, sans quoi un poste redemarre trois fois
journaliserait trois fois la meme bascule -- et emporte le staging QUELLE QUE
SOIT l'issue : une bascule annulee laisse les memes dizaines de mega-octets
qu'une reussie.

SwapBudget repond a ce que le banc a laisse ouvert. Un script qui ne demarre pas
laisse Start() rendre nil : pending.json est ecrit et aucun outcome.json
n'arrivera jamais. Sans age, ErrAlreadyRunning refuserait toute mise a jour
ulterieure, pour toujours, sur la foi d'une bascule qui n'a rien fait et rien
dit. Un instant de depart nul compte comme perime -- un fichier tronque par une
coupure de courant ne doit pas se lire "demarree a l'instant", c'est le meme mur
par une autre route.

Et ApplyUpdate porte CREATE_NO_WINDOW, pas DETACHED_PROCESS. Les deux cachent la
fenetre, un seul execute le script : mesure du 29/07, powershell.exe sort en
100 ms avec le code 0 SANS LIRE SON FICHIER, faute de console a attacher. Le
drapeau dont le nom dit "detache" est celui qui ne lance rien, et le commentaire
interdit de le retablir.
Deux defauts, dont un existant depuis toujours.

L'ECRAN CLIENT RESTAIT NOIR. Stop-OpenScaleBinaryHolders termine la tache du
kiosque avec schtasks /end, openscale-kiosk.xml ne porte QU'UN declencheur
d'ouverture de session, et personne ne la relancait -- ni install.ps1, ni
update.ps1. Apres une mise a jour, ou apres un installeur rejoue sur un poste qui
marche (le geste que TROUBLESHOOTING.md recommande), l'ecran client restait mort
jusqu'a ce que quelqu'un rouvre une session. Le defaut a survecu parce qu'un
humain qui met a jour un poste finit par le redemarrer ; un benevole qui touche
un bouton sur l'ecran d'administration, lui, regarde l'ecran client dans la
minute. Start-OpenScaleKiosk est appelee sur les quatre sorties, y compris le
retour arriere : un rollback reussi qui laisserait l'ecran noir serait une panne
creee par la reparation.

UPDATE.PS1 CONFONDAIT SES ECHECS. Un seul "exit 1" pour "restaure, le poste
marche" et "restaure, le poste est mort" -- les deux ne demandent pas la meme
chose, le premier n'appelle personne. Quatre codes maintenant, et un outcome.json
ecrit sur les quatre : au moment ou ce script se termine, le processus qui aurait
pu lire son code de retour est mort depuis une minute, puisque c'est ce script
qui l'a arrete. Le fichier est la seule chose qui traverse, et c'est le binaire
qui demarre ensuite -- le neuf ou l'ancien restaure -- qui le relit.
L'ordre EST la conception : pending.json est ecrit AVANT que le script demarre,
parce que le script arrete le service -- ce processus meme -- et que rien
d'ecrit apres ne le serait jamais. Un test l'impose depuis l'interieur du
lancement.

Trois refus qui ne se confondent pas. ErrVersionMoved fait confirmer au benevole
CE QU'IL A LU : entre le dessin de la page et l'appui, une autre version a pu
paraitre, et l'installer en silence serait installer ce que personne n'a vu.
ErrAlreadyRunning refuse la seconde bascule. Et ErrBusy remonte la phrase du
garde-fou telle quelle, via un type et non un message formate : le Hub sait s'il
s'agit d'une pesee ou d'un catalogue en attente, ce paquet ne le sait pas, et
recuperer la phrase en coupant un prefixe casserait a la premiere reformulation.

Deux facons de murer un poste, fermees toutes les deux. Une bascule perimee est
effacee au lieu d'opposer ErrAlreadyRunning pour toujours -- c'est le mode de
defaillance mesure au banc, ou un Start() rend nil sans rien lancer. Et un
lancement qui ECHOUE efface son pending.json : ce processus est encore vivant
pour le faire, et le laisser murerait le poste pour un quart d'heure a cause
d'une erreur qu'on tient dans la main.

Status lit le disque et ne sonde jamais : la page doit se dessiner tout de suite,
le sondage a son propre worker. Une bascule perimee y devient un compte rendu
synthetique plutot qu'un silence -- un benevole qui a touche le bouton et n'a rien
vu se passer merite une phrase, et "elle n'a jamais demarre" lui dit qu'il peut
reessayer.
La lecture est libre et les deux actes demandent le mot de passe : les pages de
reglage s'ouvrent en lecture et protegent l'ecriture, celle-ci ne fait pas
exception.

Deux codes la ou la conception n'en prevoyait qu'un. ERR-UPD-03 dit "attendez un
instant", ERR-UPD-09 dit "rechargez la page" : ce ne sont pas la meme
instruction, et les confondre aurait fait lire au benevole la moins utile des
deux. Le refus pour occupation remonte la phrase du garde-fou TELLE QUELLE --
le Hub sait s'il s'agit d'une pesee ou d'un catalogue en attente, cette couche
ne le sait pas, et paraphraser perdrait la seule information sur laquelle on
peut agir.

Un depot sans version publiee repond 200 et une phrase, pas une erreur de
passerelle : envoyer quelqu'un chercher une panne de reseau qu'il n'a pas coute
plus cher que de le dire.

Le service n'est cable que si les deux conditions tiennent : une version
INSTALLEE qui soit un numero -- un binaire "dev" comparé a une release donnerait
une arithmetique indefendable -- et une plateforme qui sait basculer. Sinon les
routes existent et disent honnetement qu'elles ne peuvent pas, plutot que de
laisser un benevole confirmer un acte pour rien.

Le drapeau golden de dto_test.go est renomme rewriteGolden : ce paquet importe
desormais internal/update, et une variable de paquet nommee "update" masquait le
paquet dans tout le binaire de test. L'option de ligne de commande, elle, ne
change pas -- c'est ce que les gens tapent.
Sans sondage, personne ne saurait qu'un correctif existe : c'est le scenario qui
laisse un bug six mois en production, et c'est la raison d'etre du bouton.

Le worker NE TELECHARGE RIEN. Il lit quelques kilo-octets de JSON ; l'archive ne
descend qu'au clic. Quatre postes une fois par jour sont tres loin de la limite
anonyme de soixante requetes par heure.

Un sondage rate N'ALLUME AUCUN FEU. Un magasin dont la ligne est tombee n'est pas
un poste en panne, et un feu orange la apprendrait aux benevoles a ignorer les
feux oranges. Il s'ecrit au journal en warn, sans code ERR -- ce n'est pas une
faute du poste, et lui en donner un le classerait a cote d'une imprimante
arretee.

Les deux minuteries sont enregistrees par Start, dans la goroutine appelante, et
passees au worker : c'est la discipline que suivent deja le ticker du Hub et
celui du superviseur, et un test l'a IMPOSEE ici avant qu'on la comprenne. Les
enregistrer dans la goroutine du worker faisait dependre le premier sondage du
moment ou l'ordonnanceur y arrivait -- sur une horloge fausse, c'est la
difference entre un test deterministe et un test capricieux. Trente jours
passent maintenant en 0,28 s.

internal/station ne nomme aucun type d'internal/update : le Poller rend une
chaine, et l'adaptateur qui transforme un update.Service en Poller vit dans la
racine de composition. Ou le Hub, dont le service a besoin, est resolu par une
fermeture -- les deux se veulent mutuellement, et le noeud se defait comme celui
du serveur HTTP juste au-dessus.
Neuvieme entree du rail, et non une section de la page Poste : celle-ci porte
deja "Cinq versions restaurables", qui parle des versions de la CONFIGURATION.
Deux sens du mot "version" sur un meme ecran, devant un benevole, c'est un
defaut d'usage qu'on cree soi-meme.

Le bouton NOMME la version qu'il installera, parce que c'est ce que le service
exige dans le corps de la demande : entre le dessin de la page et l'appui, une
autre version a pu paraitre, et le 409 existe pour que le benevole valide ce
qu'il a lu. Il est de la famille "irreversible" (ADR-037) : le retour arriere
est automatique sur le binaire, il ne l'est PAS sur la base.

Pendant la bascule, UNE ERREUR RESEAU EST LE CAS NOMINAL -- c'est l'inverse de
tout le reste de cet ecran. Le serveur meurt, et c'est le geste demande qui le
tue ; la traiter comme un echec afficherait une panne a l'instant precis ou tout
se passe comme prevu. L'ecran ne conclut que sur deux choses : un poste qui
repond, ou cinq minutes ecoulees.

Les notes de version ne sont pas rendues. Le corps d'une publication est du
Markdown venu du reseau : le rendre demanderait une bibliotheque de plus et
ouvrirait une injection dans l'ecran d'administration, pour un gain nul. Un lien
suffit.

Les quatre issues d'update.ps1 se disent en quatre phrases differentes, et un
test les compte : "annulee, le poste fonctionne" n'appelle personne, "le poste
ne repond pas" demande quelqu'un tout de suite.

684 tests front verts. Budget de l'ecran client inchange a 70,3 % : cette page
est dans le paquet d'administration, que la grille ne charge jamais.
Du TEXTE et non un feu. Les six feux restent ceux des peripheriques et des
ressources : un poste parfaitement sain qui s'allumerait en orange parce qu'un
correctif est sorti apprendrait aux benevoles a ignorer l'orange.

Deux tests de garde-fou existants ont impose la conception, et ils avaient raison
tous les deux. admin-two-levels tient la page benevole a UNE SEULE route : la
disponibilite voyage donc dans /admin/api/health, avec tout le reste de ce que
cette page lit, plutot que dans un second appel qui aurait elargi pour une
courtoisie ce que fait un ecran ouvert sans mot de passe. Et tokens n'inventorie
que --ink et --ink-muted comme texte : le lien est SOULIGNE et non colore, les
deux fonds pleins n'ayant ete mesures que comme fonds.

Et une panique, trouvee par la suite : le piege du NIL TYPE. newUpdateService
rend nil sur un binaire "dev", et l'affecter a une interface produit une
interface QUI N'EST PAS NULLE -- la garde `s.updater == nil` repond faux, la
methode est appelee sur un recepteur nul, et la premiere lecture de champ
panique. Trois secondes plus tard le sondage du tableau de bord recommence.
C'etait le poste de chaque developpeur, et c'aurait ete celui de tout le monde
jusqu'a la premiere version taguee. updaterFor rend une interface nulle, et un
test de non-regression interdit le retour du piege sur les deux interfaces
concernees.

686 tests front, suite Go complete verte en -race. Budget client inchange a
70,3 % : cette page est dans le paquet d'administration.
…rouves

ADR-040 et §15.5 reecrite : le bouton, les quatre issues d'update.ps1, le depot
suivi en couple owner/repo, et le modele de menace ECRIT parce qu'il est assume
-- l'empreinte vient de la meme publication que l'archive, elle prouve que le
telechargement n'a pas ete abime, pas que la publication est legitime. Ce qui
change n'est pas le risque, deja pris par tout benevole qui suit la notice, mais
le DELAI : une journee au lieu de plusieurs mois.

Le glossaire prend les identifiants du paquet et les neuf codes ERR-UPD. Et
"depot" y passe de deux sens a trois : le troisieme n'etait plus "en prose
seulement" depuis qu'il porte une cle de configuration. Les trois se
distinguent au singulier -- repositories est la couche SQLite, repository est le
depot Git, local_drop est un repertoire.

TROUBLESHOOTING donne une ligne par issue et dit ce qu'un benevole fait de
chacune : "annulee, le poste fonctionne" n'appelle personne, et c'est
l'information qui manquait le plus. INSTALLATION cesse de decrire une procedure
de console, sans la supprimer -- elle reste le seul chemin sous Linux et quand
l'ecran est inaccessible.

La spec porte les quatre points ou l'implementation l'a corrigee, chacun ne d'un
test ou d'une mesure. Et SUIVI consigne une derive documentaire de plus, relevee
en ecrivant §15.5 qui la portait aussi : trois occurrences disent encore
ProgramData\Balance et balance.db.
Trois conflits, tous resolus a la main.

SUIVI.md : les deux tetes decrivent deux evenements distincts du meme jour et
tiennent ensemble -- la livraison de la mise a jour depuis l'ecran, et
l'installation de la v0.5 par un benevole qui a bute six fois. Le journal
portait en double la ligne de la tache 0 du banc ; celle de main, plus complete,
est la seule gardee.

La specification : la version de main est celle d'avant l'implementation ("elle
n'a pas commence"). Celle de cette branche porte les quatre points ou la
construction l'a corrigee, et gagne.

Le bundle de internal/web/dist : ni l'une ni l'autre. Les deux cotes ont
reconstruit des empreintes differentes des memes sources ; les deux ont ete
effacees et le bundle RECONSTRUIT depuis les sources fusionnees, qui portent la
page Mise a jour de cette branche et les corrections de la page Poste venues de
main. C'est la seule resolution qui ne mente pas sur ce que le binaire embarque.

Suite Go complete verte en -race, 686 tests front, boundary et deps verts, les
trois cibles compilent, budget client inchange a 70,3 %.
Deux erreurs de type que ni vitest ni go test ne voient, et que la CI a
attrapees.

`let state = $state<UpdateDTO | null>(null)` : une variable nommee `state` rend
`$state` AMBIGU -- Svelte lit le prefixe `$` comme l'abonnement au store du meme
nom, si bien que la rune elle-meme devient "variable utilisee avant sa
declaration". Le fichier se compilait assez pour que les tests passent, et pas
assez pour `npm run check`. La variable s'appelle `current`, et le commentaire
dit pourquoi.

Et le rappel du bouton lisait `current.latest` : il s'execute plus tard, et
TypeScript ne peut pas savoir que `current` sera encore non nul a ce moment-la.
La version est desormais capturee dans un `$derived`, ce qui est de toute facon
plus juste -- c'est la chaine AFFICHEE que le service exige dans le corps de la
demande.

Ma verification omettait `npm run check`, que la CI lance entre `npm ci` et
`vitest`. Elle ne l'omet plus.
Trois fichiers non formates. `go vet` ne verifie pas le format et `make test`
non plus : c'est une etape a part du job "Tests et frontieres", et ma
verification l'omettait.

Le reformatage a montre une vraie faute au passage. Dans cmd/openscale/update.go,
guardFunc avait ete insere ENTRE le commentaire de newUpdateService et sa
fonction : les deux godoc s'etaient soudes, et godoc aurait affiche sous
guardFunc les deux cas ou le service ne se construit pas. Le type passe avant, le
commentaire retrouve sa fonction.

Chaine de la CI rejouee localement dans son ordre : gofmt, vet, race -short,
CGO_ENABLED=0 -short, boundary, deps, et les cinq planchers de couverture --
domain 96,9 / store 86,5 / scale 99,4 / printing 94,4 / catalog 89,4.
@lostmind84
lostmind84 merged commit 021e4b6 into main Jul 29, 2026
6 checks passed
@lostmind84
lostmind84 deleted the design/mise-a-jour-depuis-admin branch July 31, 2026 11:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant