Compare commits

...

3 commits

Author SHA1 Message Date
le king fu
97d376b83c docs(qa): fix the Linux state sequence the checklist had backwards
All checks were successful
PR Check — Rust / rust (pull_request) Successful in 9m14s
`downloadAndInstall` is one call that downloads AND installs
(updater.rs:723-729), and the `Finished` event is a no-op in
useUpdater.ts:119-121. `READY_TO_INSTALL` is therefore dispatched only
after `dpkg -i` returns, so the pkexec prompt opens while the card still
reads "Téléchargement en cours…".

The checklist told the tester to tick `readyToInstall` before the prompt.
That ordering neutralized the polkit precondition the page exists to
enforce: with no agent the app hangs in `downloading`, not in
`readyToInstall`, so a tester following the checklist files "the download
stalls" instead of "no polkit agent" — the exact false trace this page is
written to prevent.

Also states plainly that at `readyToInstall` the .deb is already on disk
and the button only calls `relaunch()`; the label misleads on this target.

Three smaller corrections from the same review:

- The update triggers are four manual sites, not two: UpdateCard idle /
  upToDate refresh / error retry, plus ErrorPage.tsx:82 — which only
  detects and offers no download path. The load-bearing claim, "no
  automatic check in the app", was already right.
- "Si ça casse" now spells out that DELETE and PUT hit different API
  prefixes (/api/v1/packages/ vs /api/packages/, release.yml:217-219,
  which carries a comment about exactly this). Replaying both against one
  URL 404s at the worst possible moment.
- The SKILL.md changelog entry moves back into date order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 11:51:51 -04:00
le king fu
f6418f79cd docs(release): add a real update-cycle QA checklist to step 9
Step 9 inspected latest.json and stopped there. That proves the file is well
formed, not that it installs: the TLS download, signature verification,
installer execution and relaunch are exercised by no test at all — cargo check
and cargo test only prove that code compiles. A regression there surfaces at
update time, on a user's machine, and automatic updates are a Base+ feature.

Written as a manual checklist rather than automation because it needs two real
desktop environments. The value is in the preconditions, which are what make
the difference between running the test and only appearing to:

- Test from a machine still on the PREVIOUS version. check() compares
  release.version > current strictly, so testing on the machine that just
  built the release shows "up to date" and the checklist gets ticked as
  "nothing to update" — the silent skip it exists to prevent, now with a
  paper trail claiming it passed.
- A Base+ key on each machine, with any activation.token from another machine
  removed. Auto-update is entitlement-gated: useUpdater dispatches NOT_ENTITLED
  before check() ever runs, and a token bound to a different machine_id
  silently resolves the edition back to Free.
- A live polkit agent on Linux, and record which prompt actually appeared —
  install_deb cascades pkexec -> zenity/kdialog -> terminal sudo, so without an
  agent the app appears to hang instead of failing.

The two target flows are written separately because they genuinely differ: on
Windows downloadAndInstall never returns (the process exits and NSIS takes
over, so readyToInstall/installing never render), while on Linux the app stays
alive through a polkit prompt and a restart control.

The doc states what the test does NOT prove: `tar` stays unexercised. Its
vulnerable path is only reached by install_appimage and the macOS .app.tar.gz
branch; our .deb goes through `pkexec dpkg -i` and our NSIS path launches the
.exe. That corrects #315's own premise.

The failure path is documented as an incident playbook rather than a
"rollback", because it is not one. Republishing the previous latest.json stops
propagation, but check()'s strict comparison means users already on the broken
version are never offered the older one — the real fix is a vN+1.

rpm is recorded as known-broken, not unverified, and tracked in #320.

Resolves #315

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 11:51:51 -04:00
le king fu
2d4caecae8 fix(deps): resolve the quick-xml advisories, unyank deep-link and spin
All checks were successful
PR Check — Rust / rust (pull_request) Successful in 9m23s
The removal trigger #312 was written for had already fired — I filed the issue
without checking whether a newer plist existed. plist 1.10.0 ships quick-xml
0.41.0, which carries the fix, within tauri's existing bound:

    cargo update -p plist -> plist 1.8.0 -> 1.10.0
                             quick-xml 0.38.4 -> 0.41.0

So RUSTSEC-2026-0194 and -0195 are resolved rather than accepted, and leave
.cargo/audit.toml the day they entered it. rsa is now the only entry, and the
guard loops on that crate alone; its rationale comment is re-pointed
accordingly, since it was written entirely around quick-xml/plist.

Also bumps the two yanked crates (#313). tauri-plugin-deep-link 2.4.8 -> 2.4.9:
upstream's 2.4.9 is a single commit, "Fix broken iOS custom URL schemes", so
the defect behind the yank is iOS-only and never reached this desktop app —
v0.14.0 shipping 2.4.8 was not a user-facing problem, which is why neither
Security nor Fixed applies to it in the changelog. spin 0.9.8 -> 0.9.9; every
0.9.x up to 0.9.8 is yanked, which reads as a bulk yank rather than a defect.

The #310 changelog bullet is amended rather than contradicted: it sits in the
same unreleased section and would otherwise ship two opposing claims in the
same release notes. Two of its statements were wrong. It said three advisories
remained (now one), and it said tar sits on "real code paths in the shipped
app" — tar is compiled, but its vulnerable extraction path is only reached by
the AppImage and macOS installers this project does not bundle. The
rustls-webpki half stands: TLS runs on every update check.

ADR 0018's decision is untouched; an amendment header marks the passages that
are now historical, including the "override is impossible" alternative, which
plist 1.10.0 made false the same day.

cargo audit from the repo root: 0 vulnerabilities, warnings 23 -> 21 (the two
yanked ones). From src-tauri/ it reports 1 — that is the cwd sensitivity of
.cargo/audit.toml, not a regression. Guard: 2 checks + canary, exit 0.
cargo check + cargo test green (106 tests). Lock diff: 4 packages, 666 before
and after.

Resolves #312
Resolves #313

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 11:51:46 -04:00
8 changed files with 143 additions and 44 deletions

View file

@ -29,26 +29,14 @@
# Empty output on both targets means the entry is still justified. Any output
# means it is not: drop the entry and fix the advisory for real.
# The quick-xml pair (RUSTSEC-2026-0194 / -0195) used to live here and was
# removed on 2026-07-27 by #312: plist 1.10.0 ships quick-xml 0.41.0, which
# carries the fix, so the advisories were resolved rather than accepted. That is
# the intended lifecycle of an entry in this file — it leaves when a fix becomes
# reachable, not when someone remembers to look.
[advisories]
ignore = [
# quick-xml 0.38.4 — RUSTSEC-2026-0194 (quadratic run time when checking a
# start tag for duplicate attribute names) and RUSTSEC-2026-0195 (unbounded
# namespace-declaration allocation enabling memory-exhaustion DoS), 7.5
# high each.
#
# Not compiled on either shipped target. quick-xml is pulled by `plist`,
# which tauri only depends on for Apple bundling: both per-target trees are
# empty, and it appears solely under x86_64-apple-darwin, which we do not
# ship.
#
# An override is not an option either — both advisories are patched only in
# >= 0.41.0 while plist requires ^0.38, a semver-incompatible boundary that
# `[patch.crates-io]` cannot cross.
#
# Removal trigger: plist (or tauri) shipping quick-xml >= 0.41.0 — see #312.
"RUSTSEC-2026-0194",
"RUSTSEC-2026-0195",
# rsa 0.9.10 — RUSTSEC-2023-0071 (Marvin attack: potential key recovery
# through timing side channels), 5.9 medium.
#

View file

@ -2,7 +2,7 @@
name: release
description: Release a new version of Simpl-Resultat (bump, changelog, tag, push)
user-invocable: true
updated: 2026-07-13
updated: 2026-07-27
---
# /release — Release Simpl-Resultat
@ -41,6 +41,13 @@ updated: 2026-07-13
9. **Post-CI — vérifier la release publiée.** Surveiller `release.yml` (outil `Monitor` sur le run), puis vérifier la release réellement attachée — `status=success` du workflow ne suffit pas :
- Les **7 artefacts** attendus : `.exe` NSIS, `.deb`, `.rpm`, leurs 3 signatures `.sig`, et `latest.json`.
- Le contenu de `latest.json` (il pilote l'auto-update des installations existantes) : champ `version` correct, signatures non vides pour les deux plateformes, URLs pointant vers les bons binaires, notes extraites du CHANGELOG.
10. **Post-CI — dérouler un vrai cycle de mise à jour** si la release touche `tauri-plugin-updater`, ses dépendances (`reqwest`/`rustls-webpki`, `tar`), `release.yml`, ou la config `updater` de `tauri.conf.json` : [`docs/qa-update-cycle.md`](../../../docs/qa-update-cycle.md).
Inspecter `latest.json` prouve qu'il est **bien formé**, pas qu'il **installe**. Le reste de la chaîne — téléchargement TLS, vérification de signature, exécution de l'installeur, redémarrage — n'est exercé par aucun test : `cargo check`/`cargo test` prouvent que ce code compile, rien de plus. Une régression s'y voit chez l'utilisateur, au moment de la mise à jour.
Deux pièges qui font cocher la checklist sans rien vérifier, détaillés dans la doc : tester depuis une machine **déjà à la nouvelle version** (`check()` répond « à jour ») et tester sans **clé Base+** (l'auto-update est verrouillé par édition, l'app bascule en `notEntitled` avant même d'interroger le serveur).
Si la checklist n'est pas déroulée, **l'écrire avec la raison** sur la release plutôt que de laisser le silence.
## Regles
@ -55,3 +62,4 @@ updated: 2026-07-13
- 2026-04-19 — Added Cargo.lock + package-lock.json to bump list, `npm install --package-lock-only` fallback when lockfile stale, explicit `[Unreleased]` migration pattern, annotated tags (#102/#112 release cycle)
- 2026-07-01 — Documenter que le header FR est `## [Non publié]` (≠ `[Unreleased]`), pour éviter le faux diagnostic « changelog FR vide » lors de la migration. Source : session 5466da98.
- 2026-07-13 — Étape 0 (pré-vol) : revalider le tip localement avant de tagger — `check.yml` ne tourne pas sur `main`, le tip mergé n'a jamais été vu par le CI ; vérifier `.claude/worktrees/` vide (vitest récurse sinon). Étape 9 (post-CI) : vérifier la release publiée — 7 artefacts attendus + contenu de `latest.json` (pilote l'auto-update) ; `status=success` ne suffit pas. Règle : tagger publie vers l'extérieur (updater automatique) → confirmer avec Max avant de tagger. Source : session fdda84cb (release v0.13.0).
- 2026-07-27 — Étape 10 (post-CI) : dérouler un vrai cycle de mise à jour via `docs/qa-update-cycle.md` quand la release touche l'updater. Inspecter `latest.json` prouve qu'il est bien formé, pas qu'il installe : le téléchargement TLS, la vérification de signature, l'exécution de l'installeur et le redémarrage ne sont exercés par aucun test. Deux pièges qui font cocher sans vérifier — tester depuis la machine déjà à jour (`check()` répond « à jour ») et tester sans clé Base+ (auto-update verrouillé par édition, `notEntitled` avant toute requête). Source : session 50ac88d9 (#315, découvert en traitant les advisories `tar`/`rustls-webpki` de #310).

View file

@ -75,11 +75,13 @@ jobs:
# Anti-rot guard for the suppressions in .cargo/audit.toml (#310). Each
# entry there is justified by its crate being absent from every shipped
# target's graph — a property of today's resolved graph, not a permanent
# one. If a tauri/plist bump ever makes quick-xml unconditional, the
# suppression would silently hide a live advisory and the daily audit
# would stay green: the inverse of the permanent red #310 exists to kill.
# one. rsa is currently reachable from nothing: its only parent in the
# lockfile is sqlx-mysql, which this SQLite project never compiles. If a
# dependency change ever pulls it into a shipped target, the suppression
# would silently hide a live advisory and the daily audit would stay
# green: the inverse of the permanent red #310 exists to kill.
#
# That bump would itself be a src-tauri change, which is exactly what
# Such a change would itself touch src-tauri, which is exactly what
# triggers this workflow. Runs after cargo check so the registry index is
# already warm, and --locked so cargo tree cannot rewrite the lockfile the
# audit was taken against.
@ -94,7 +96,7 @@ jobs:
- name: Verify suppressed advisories are still unreachable
run: |
set -u
CRATES="quick-xml rsa"
CRATES="rsa"
TARGETS="x86_64-unknown-linux-gnu x86_64-pc-windows-msvc"
rc=0
checks=0

View file

@ -28,7 +28,7 @@
### Sécurité
- Mise à jour de deux dépendances Rust situées sur le chemin de la mise à jour automatique, corrigeant six advisories RustSec : `rustls-webpki` 0.103.9 → 0.103.13 (traitement des contraintes de nom de certificat et analyse des listes de révocation, dont une panique atteignable) et `tar` 0.4.44 → 0.4.46 (`chmod` de répertoires arbitraires en suivant des liens symboliques pendant l'extraction, et en-têtes de taille PAX mal pris en compte). Les deux sont tirées par `tauri-plugin-updater`, qui télécharge et décompresse les mises à jour de l'application : ce sont donc de vrais chemins de code du produit livré. Aucun changement de comportement. Trois autres advisories restent signalées sur le fichier de verrouillage mais ne s'appliquent pas au produit livré — `quick-xml` n'est compilé que pour des cibles Apple que nous ne livrons pas, et `rsa` n'est jamais compilé — elles sont désormais consignées comme acceptées, avec leur justification et les conditions de leur retrait, plutôt que de laisser l'audit de sécurité quotidien rouge en permanence (#310).
- Mise à jour de deux dépendances Rust situées sur le chemin de la mise à jour automatique, corrigeant six advisories RustSec : `rustls-webpki` 0.103.9 → 0.103.13 (traitement des contraintes de nom de certificat et analyse des listes de révocation, dont une panique atteignable) et `tar` 0.4.44 → 0.4.46 (`chmod` de répertoires arbitraires en suivant des liens symboliques pendant l'extraction, et en-têtes de taille PAX mal pris en compte). Les deux sont tirées par `tauri-plugin-updater` : celle qui gère TLS travaille à chaque vérification et à chaque téléchargement de mise à jour, tandis que celle qui décompresse n'est atteinte que par des formats d'installeur que cette application ne livre pas — elle a été mise à jour quand même, plutôt que contournée par un raisonnement. Aucun changement de comportement. Une autre advisory reste signalée sur le fichier de verrouillage et ne s'applique pas au produit livré : `rsa` n'est jamais compilé, son seul parent étant un pilote MySQL qu'une application SQLite ne construit jamais. Elle est consignée comme acceptée, avec sa justification et les conditions de son retrait, plutôt que de laisser l'audit de sécurité quotidien rouge en permanence. Les deux advisories visant `quick-xml` ont été acceptées de la même façon pendant quelques heures, puis résolues pour de bon : `plist` 1.10.0 livrait déjà le `quick-xml` 0.41.0 corrigé (#310, #312).
- Mise à jour de la dépendance de build `postcss` (8.5.13 → 8.5.23), corrigeant GHSA-r28c-9q8g-f849 (traversée de chemin lors du chargement automatique d'une source map précédente depuis un commentaire `sourceMappingURL`, menant à la divulgation de fichiers `.map` arbitraires). Outillage de build uniquement, aucun changement runtime ni de comportement — le CSS généré est identique octet pour octet. Une advisory reste signalée sur `react-router` et est volontairement laissée en l'état : elle vise le mode React Server Components, qu'une application de bureau locale n'exécute jamais, et aucun correctif n'existe vers lequel avancer puisque `react-router-dom` est figé en 7.18.1 (sortir de la plage concernée demanderait une migration vers react-router v8, pas un changement de version) (#311).
## [0.14.0] - 2026-07-18

View file

@ -28,7 +28,7 @@
### Security
- Updated two Rust dependencies sitting on the automatic-update path, clearing six RustSec advisories: `rustls-webpki` 0.103.9 → 0.103.13 (certificate name-constraint handling and certificate-revocation-list parsing, including a reachable panic) and `tar` 0.4.44 → 0.4.46 (arbitrary directory `chmod` by following symlinks during extraction, and mishandled PAX size headers). Both are pulled in by `tauri-plugin-updater`, which downloads and unpacks application updates, so these are real code paths in the shipped app. No behaviour change. Three further advisories are still reported against the lockfile but do not apply to the shipped product — `quick-xml` is only compiled for Apple targets we do not ship, and `rsa` is never compiled at all — so they are now recorded as accepted, together with their justification and the conditions for removing them, instead of leaving the daily security audit permanently red (#310).
- Updated two Rust dependencies sitting on the automatic-update path, clearing six RustSec advisories: `rustls-webpki` 0.103.9 → 0.103.13 (certificate name-constraint handling and certificate-revocation-list parsing, including a reachable panic) and `tar` 0.4.44 → 0.4.46 (arbitrary directory `chmod` by following symlinks during extraction, and mishandled PAX size headers). Both are pulled in by `tauri-plugin-updater`: the TLS one runs on every update check and download, while the archive one is only reached by installer formats this app does not ship — it was updated anyway rather than reasoned around. No behaviour change. One further advisory is still reported against the lockfile and does not apply to the shipped product: `rsa` is never compiled at all, its only parent being a MySQL driver that a SQLite application never builds. It is recorded as accepted, with its justification and the conditions for removing it, instead of leaving the daily security audit permanently red. The two advisories against `quick-xml` were accepted the same way for a few hours, then resolved outright: `plist` 1.10.0 turned out to ship the fixed `quick-xml` 0.41.0 (#310, #312).
- Updated the build-time dependency `postcss` (8.5.13 → 8.5.23), clearing GHSA-r28c-9q8g-f849 (path traversal while auto-loading a previous source map from a `sourceMappingURL` comment, leading to arbitrary `.map` file disclosure). Build tooling only, no runtime or behaviour change — the generated CSS is byte-for-byte identical. One advisory remains reported against `react-router`, and is deliberately left as is: it targets the React Server Components mode, which a local desktop app never runs, and no fix exists to move forward to since `react-router-dom` is frozen at 7.18.1 (leaving the affected range would mean migrating to react-router v8, not bumping a version) (#311).
## [0.14.0] - 2026-07-18

View file

@ -5,6 +5,10 @@
- Issues: #310 (les 9 advisories découvertes, cette décision), #312 (déclencheur de retrait des entrées `quick-xml`), #314 (le cron de `audit.yml` n'a jamais démarré)
- S'appuie sur #232 (split de `check.yml`, création de `audit.yml`, retrait du `|| true` qui masquait la sortie de `cargo audit`)
> **Amendement du 2026-07-27 (#312/#313)** — la décision et ses trois règles sont inchangées ; seules les entrées ont bougé. Les deux advisories `quick-xml` ont **quitté la liste le jour même** : `plist` 1.10.0 tire `quick-xml` 0.41.0, qui porte le correctif, dans la borne existante de `tauri`. Elles sont donc **résolues, pas acceptées**, et le mécanisme a fonctionné comme prévu — la vérification par cible a montré qu'un correctif était devenu atteignable, et l'entrée est partie. Il ne reste que `RUSTSEC-2023-0071` (`rsa`), et le garde-fou ne boucle plus que sur ce crate.
>
> Trois passages ci-dessous sont **datés du 2026-07-27 avant ce bump** et à lire comme historiques : `quick-xml` comme exemple de l'insuffisance de `--target all` (règle 1), l'entrée `quick-xml` du tableau de contexte, et l'alternative « override / `[patch.crates-io]` » — cette dernière **n'est plus vraie**, `plist` 1.10.0 déclarant désormais `quick-xml ^0.41.0`. L'argument de fond qu'elle illustre (`[patch.crates-io]` ne franchit pas une frontière semver-incompatible) reste correct, seul le cas d'espèce a disparu.
## Contexte
#232 a retiré le `|| true` qui avalait la sortie de `cargo audit` et créé `audit.yml`, un job quotidien (06:00 UTC) **volontairement bloquant** : il *est* le canal de notification pour les advisories publiées entre deux PR Rust, `check-rust.yml` ne tournant que sur ~1 PR sur 40.
@ -51,7 +55,7 @@ Trois détails la rendent fiable plutôt que décorative :
- un **canari** (`tar`, dépendance réellement présente) doit être trouvé à chaque exécution, faute de quoi le silence de la boucle ne prouve rien ;
- `.cargo/**` est ajouté aux `paths` de `check-rust.yml` pour qu'une PR ne touchant que la liste déclenche bien la vérification.
Le pendant — une entrée devenue *inutile*, que rien ne signale — est couvert par une issue de suivi portant la condition de retrait (#312 pour `quick-xml`).
Le pendant — une entrée devenue *inutile*, que rien ne signale — est couvert par une issue de suivi portant la condition de retrait : #312 l'a joué pour `quick-xml` (et a servi : le déclencheur avait sauté sans que personne le remarque), #319 le fait pour `rsa`. **Toute entrée de la liste doit en avoir une**, sinon le garde-fou ne couvre qu'une moitié du risque.
## Alternatives considérées
@ -59,7 +63,7 @@ Le pendant — une entrée devenue *inutile*, que rien ne signale — est couver
- **Remettre `continue-on-error` ou `|| true` sur le job quotidien.** Rejeté pour la même raison, en pire : cela supprime le signal pour *toutes* les advisories, pas seulement pour les trois inévitables.
- **Dupliquer des `--ignore` dans `audit.yml` et `check-rust.yml`.** Rejeté : deux listes à maintenir en phase, sans endroit naturel où écrire la justification. Le fichier `.cargo/audit.toml` est lu par les deux workflows sans qu'aucun n'ait à le savoir, et il porte les preuves à côté des entrées.
- **Ignorer au niveau du crate plutôt que de l'advisory.** Rejeté — voir la règle 2.
- **Un override / `[patch.crates-io]` pour `quick-xml`.** Impossible : le correctif est en `0.41.0` et `plist` exige `^0.38`. `[patch.crates-io]` ne franchit pas une frontière semver-incompatible.
- **Un override / `[patch.crates-io]` pour `quick-xml`.** Impossible : le correctif est en `0.41.0` et `plist` exige `^0.38`. `[patch.crates-io]` ne franchit pas une frontière semver-incompatible. *(Périmé le jour même — `plist` 1.10.0 déclare `quick-xml ^0.41.0`, et un simple `cargo update -p plist` a suffi ; voir l'amendement en tête. Le principe reste, le cas d'espèce a disparu.)*
- **Retirer `sqlx-mysql` du graphe pour éliminer `rsa`.** Écarté : `rsa` n'étant compilé sur aucune cible, l'opération serait un contorsionnement du manifeste pour un gain nul.
## Conséquences
@ -87,4 +91,4 @@ Le pendant — une entrée devenue *inutile*, que rien ne signale — est couver
- [`.forgejo/workflows/check-rust.yml`](../../.forgejo/workflows/check-rust.yml) — étape « Verify suppressed advisories are still unreachable »
- [`.forgejo/workflows/audit.yml`](../../.forgejo/workflows/audit.yml) — le gate quotidien bloquant
- [ADR 0009](0009-proxy-price-fetching-via-maximus-api.md) — récupération de cours via `reqwest`, l'un des deux chemins qui rendent `rustls-webpki` atteignable
- Issues #310 (cette décision), #312 (retrait `quick-xml`), #313 (`tauri-plugin-deep-link` yanked), #314 (cron jamais déclenché), #315 (smoke-test du cycle de mise à jour)
- Issues #310 (cette décision), #312 (retrait `quick-xml`, **fait le 2026-07-27**), #313 (`tauri-plugin-deep-link` yanked, fait), #314 (cron jamais déclenché), #315 (smoke-test du cycle de mise à jour), #319 (déclencheur de retrait `rsa`, la dernière entrée restante)

97
docs/qa-update-cycle.md Normal file
View file

@ -0,0 +1,97 @@
# QA — Cycle de mise à jour automatique
Checklist manuelle pour valider qu'une release est réellement installable **par la mise à jour automatique**, et pas seulement téléchargeable. À dérouler après chaque release qui touche `tauri-plugin-updater`, ses dépendances (`reqwest`/`rustls-webpki`, `tar`), `release.yml`, ou la configuration `updater` de `tauri.conf.json`.
Elle existe parce que la CI ne peut rien prouver ici : `cargo check` et `cargo test` prouvent que le chemin de mise à jour **compile**, jamais qu'il fonctionne. Une régression s'y manifeste chez l'utilisateur, au moment de la mise à jour — et la mise à jour automatique étant une fonctionnalité **Base+**, ce sont des utilisateurs payants.
> **Cette checklist tourne après que la release est publiée.** `release.yml` a déjà poussé `latest.json` vers le registre de paquets, qui est l'endpoint configuré dans `tauri.conf.json`. Tout utilisateur Base+ qui clique sur « Vérifier les mises à jour » reçoit donc déjà la nouvelle version pendant que vous déroulez ceci. En cas d'échec, voir « Si ça casse » en bas — et lire cette section **avant** de commencer, pas pendant l'incident.
---
## Prérequis
Ce sont les conditions sans lesquelles le test ne teste rien. Chacune a un mode d'échec où la checklist se coche « OK » sans avoir rien vérifié.
- [ ] **Une machine Windows** (hôte ou VM) et **une machine Linux de bureau**. Les deux flux sont réellement différents, ils ne se substituent pas l'un à l'autre.
- [ ] Sur la machine Linux, **un agent polkit qui tourne**. L'installation du `.deb` passe par `pkexec` ; sans agent, l'app retombe en cascade sur `zenity`/`kdialog` puis sur un `sudo` dans un terminal inexistant, et **paraît figée au lieu d'échouer — sur l'écran de téléchargement, pas sur `readyToInstall`** (voir §2, c'est le piège qui produit le mauvais rapport de bug). Noter plus bas quelle invite est réellement apparue : si ce n'est pas polkit, c'est un chemin de repli qui a été validé, pas le chemin nominal.
- [ ] **La version précédente installée sur chaque machine**, depuis les artefacts de la release précédente (`.deb` et `-setup.exe` attachés à la release Forgejo). Ne pas tester sur la machine qui vient de builder la nouvelle version.
> Sans ça, `check()` compare `release.version > version_courante`, trouve faux, et l'UI affiche « à jour ». La checklist se coche alors comme « rien à mettre à jour » — soit exactement le saut silencieux qu'elle est censée éliminer, avec en prime une trace écrite affirmant qu'elle est passée.
- [ ] **Une clé de licence Base ou Premium valide sur chaque machine.** La mise à jour automatique est verrouillée par édition : `useUpdater` appelle `check_entitlement("auto-update")` et bascule en `notEntitled` **avant** même d'appeler `check()`. Une installation Gratuite ne télécharge jamais rien, et les builds de release n'ont pas de `dev-override`.
- [ ] Sur chaque machine, **supprimer tout `activation.token` provenant d'une autre machine** (VM clonée, snapshot restauré) dans le répertoire de données de l'app. Une `license.key` seule fonctionne — l'absence de token est un état de pré-activation toléré — mais un token émis pour un autre `machine_id` fait retomber l'édition en Gratuite, et le testeur voit une carte « non éligible » anodine plutôt qu'un échec.
---
## Ce que ce test prouve — et ce qu'il ne prouve pas
À écrire explicitement, parce que la tentation est de croire qu'un cycle réussi couvre tout le chemin de mise à jour.
**Prouvé** sur les deux cibles : le téléchargement TLS (donc `rustls-webpki`), la vérification de signature minisign, l'exécution de l'installeur, et le redémarrage sur la nouvelle version.
**Non prouvé** : l'extraction d'archive, donc `tar`. Dans `tauri-plugin-updater`, `tar` n'est atteint que par `install_appimage` et la branche macOS `.app.tar.gz`. Aucun de nos bundles ne passe par là — le `.deb` fait `pkexec dpkg -i`, le NSIS écrit le `.exe` dans un répertoire temporaire et le lance. **Tant qu'on ne livre pas d'AppImage, le chemin `tar` reste mort** et aucune checklist manuelle ne le couvrira.
---
## 1. Windows (NSIS)
Point d'entrée : **Paramètres → Systèmes → carte Mises à jour → « Vérifier les mises à jour »**. Il n'y a **aucune vérification automatique dans l'app** : les quatre déclencheurs sont manuels — ce bouton, l'icône rafraîchir de l'état `upToDate`, le « réessayer » de l'état `error`, et celui de la page d'erreur, qui **détecte seulement** et n'offre aucun chemin de téléchargement.
- [ ] La carte passe en `checking`, puis affiche `available` avec le **numéro de la nouvelle version** et les notes extraites du CHANGELOG.
- [ ] Cliquer sur télécharger → état `downloading`, **la progression avance** (elle vient de `contentLength`, donc un serveur qui ne le renvoie pas se voit ici).
- [ ] L'installeur NSIS s'ouvre et **l'application se ferme d'elle-même**.
> Attendu, et propre à Windows : `downloadAndInstall` ne rend jamais la main — le processus appelle `exit(0)` et laisse l'installeur prendre le relais. Les états `readyToInstall` et `installing` **ne s'affichent jamais** sur cette cible. Ne pas les attendre, ne pas les cocher.
- [ ] Terminer l'installation, relancer l'app, vérifier la version dans Paramètres → Systèmes.
- [ ] Re-cliquer sur « Vérifier les mises à jour » → `upToDate`.
## 2. Linux (.deb)
- [ ] Même point d'entrée, mêmes états jusqu'à `downloading`.
- [ ] **Pendant que la carte affiche encore « Téléchargement en cours… »**, une invite d'authentification apparaît. **Noter laquelle** : polkit (nominal), zenity/kdialog, ou rien du tout (repli `sudo` → l'app reste figée **sur l'écran de téléchargement**).
Invite observée : `________________`
> C'est bien le moment nominal, et c'est contre-intuitif : `downloadAndInstall` télécharge **et** installe dans le même appel (`updater.rs:723-729`), et l'événement `Finished` est un no-op côté UI (`useUpdater.ts:119-121`). L'écran reste donc sur `downloading` pendant tout le `dpkg -i`. Un testeur qui guette l'invite **après** `readyToInstall` ne la verra jamais arriver au bon moment : il rapportera « le téléchargement bloque » là où le vrai diagnostic est « pas d'agent polkit ».
- [ ] Authentifier → l'app **reste vivante** et passe en `readyToInstall` (« Mise à jour prête à installer » + bouton « Installer et redémarrer »).
> À ce stade le `.deb` est **déjà installé sur le disque** : `installAndRestart` ne fait que `relaunch()`. Le libellé du bouton ment sur cette cible — qui s'arrête là croit que rien n'a été installé, et qui voit la version changer après le clic attribue l'installation au clic.
- [ ] Cliquer → état `installing`, l'app redémarre.
- [ ] Vérifier la version, re-vérifier → `upToDate`.
## 3. RPM — cassé, connu
- [ ] **Ne pas tester** : ce chemin est cassé, pas seulement non vérifié.
`latest.json` ne porte qu'une entrée `linux-x86_64`, construite à partir du `.deb` (`release.yml:104-122`). Une installation rpm reçoit donc des octets deb, que `Installer::Rpm` rejette après vérification de la signature magique du payload. Les artefacts `.rpm` continuent pourtant d'être publiés comme assets de release. Suivi en **#320**.
---
## Si ça casse
L'ordre compte, et la première action ne répare pas ce qu'on croit.
1. **Republier le `latest.json` précédent** sur `generic/simpl-resultat/latest` (`DELETE` puis `PUT`, avec le `PACKAGE_TOKEN` qu'utilise `release.yml`). Le corps est récupérable depuis les assets de la release précédente — sa copie dans le registre a été détruite par le `DELETE` que `release.yml` fait avant chaque envoi.
**Les deux verbes ne visent pas le même préfixe d'API**, et `release.yml:217-219` porte un commentaire explicite là-dessus — le piège est déjà tombé une fois :
```
DELETE {serveur}/api/v1/packages/{owner}/generic/simpl-resultat/latest
PUT {serveur}/api/packages/{owner}/generic/simpl-resultat/latest
```
Rejouer les deux sur la même URL donne un 404 sur l'un des deux, au moment où on peut le moins se permettre de le débugger.
2. **Comprendre ce que ça arrête, et ce que ça n'arrête pas.** `check()` compare strictement `release.version > version_courante`. Republier vN-1 **stoppe la propagation** vers ceux qui n'ont pas encore cliqué. Les utilisateurs déjà passés à la vN cassée ne se verront **jamais** proposer vN-1 : ils sont bloqués dessus.
3. **Le vrai correctif est donc une vN+1**, pas un retour en arrière. Le republication de vN-1 n'achète que du temps — et si elle reste en place, elle prive aussi les utilisateurs restés en vN-1 de tout chemin de mise à jour.
---
## Trace
- [ ] Consigner le résultat en commentaire de la release Forgejo : cibles déroulées, versions de départ et d'arrivée, invite d'authentification observée sous Linux.
- [ ] Si la checklist **n'a pas été déroulée**, l'écrire explicitement avec la raison, plutôt que de laisser le silence. Une étape non tracée est indiscernable d'une étape passée — c'est le mode d'échec que cette page combat.

30
src-tauri/Cargo.lock generated
View file

@ -589,7 +589,7 @@ version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.61.2",
]
[[package]]
@ -989,7 +989,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.59.0",
"windows-sys 0.61.2",
]
[[package]]
@ -1209,7 +1209,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@ -3498,9 +3498,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "plist"
version = "1.8.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85"
dependencies = [
"base64 0.22.1",
"indexmap 2.13.0",
@ -3648,9 +3648,9 @@ dependencies = [
[[package]]
name = "quick-xml"
version = "0.38.4"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
dependencies = [
"memchr",
]
@ -4044,7 +4044,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@ -4100,7 +4100,7 @@ dependencies = [
"security-framework",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@ -4645,9 +4645,9 @@ dependencies = [
[[package]]
name = "spin"
version = "0.9.8"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
dependencies = [
"lock_api",
]
@ -5206,9 +5206,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-deep-link"
version = "2.4.8"
version = "2.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3db49816aee496a9b200d55b55ab6ae73fd50847c79f2fabc7ee20871fa75c95"
checksum = "70ee75bc5627f77bfdf40c913255ebc258117b10ebe2b2239a1a1cf40b0b58aa"
dependencies = [
"dunce",
"plist",
@ -5477,7 +5477,7 @@ dependencies = [
"getrandom 0.3.4",
"once_cell",
"rustix",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@ -6323,7 +6323,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.61.2",
]
[[package]]