state: sync after #348 (rustls RUSTSEC-2026-0285, daily audit green again)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
le king fu 2026-09-21 20:36:59 -04:00
parent 9abf4743e5
commit 14f1d3b226

View file

@ -1,8 +1,8 @@
# STATE — Simpl'Résultat # STATE — Simpl'Résultat
> Derniere MAJ : 2026-09-13 (**Étape 10 de v0.15.0 consignée** — Max rapporte que la mise à jour automatique v0.14.0 → v0.15.0 fonctionne chez les deux testeurs. Corroboration Forgejo : assets v0.15.0 à exactement 1 `.deb` + 1 `-setup.exe` téléchargés, 0 `.sig` — cohérent avec une machine Linux et une Windows passées par l'updater, sans en être la preuve ; invite polkit Linux non consignée ; `.rpm` hors test (#320). Trace rédigée pour le **corps** de la release (Forgejo n'a pas de commentaires de release ; le `PATCH` est une écriture externe bloquée en mode auto → remis à Max). **Le « code à 6 chiffres » reçu par courriel par un testeur n'est pas une étape de la mise à jour** : ni l'updater, ni la saisie de clé, ni l'activation machine n'en émettent — c'est le code de vérification Logto du bouton « Se connecter » de la carte Compte Maximus (Paramètres → Utilisateurs, juste au-dessus de la carte Licence), pour l'un de trois flux : connexion par courriel + code, mot de passe oublié, ou inscription ; MFA = TOTP seul. Le flux exact est dans le journal d'audit Logto (secret M2M sur le VPS seulement). Sans effet sur l'édition Premium. Cause probable : le texte de la carte dit « Le compte est requis uniquement pour les fonctionnalités Premium », faux depuis que Premium vient de la clé — à reformuler.) > Derniere MAJ : 2026-09-21 (**RUSTSEC-2026-0285 corrigée, l'audit quotidien de nouveau vert** — `rustls` 0.23.36 → 0.23.45, PR #349 mergée en rebase ff sur `main` `9abf474` (SHA inchangé, la branche était directement au-dessus de `main`), #348 fermée. `audit.yml` échouait depuis le **2026-09-15**, 7 runs (405→411), dernier vert le 404 du 09-14, **aucun push sur `main` dans la fenêtre** — deuxième occurrence du mode d'échec de #343 (17 jours en août, 7 ici) : le gate crie, personne ne regarde. Vert **prouvé** par `workflow_dispatch` sur `main` (run 414 : 666 paquets, zéro vulnérabilité, 12 warnings autorisés), pas déduit du run de PR — l'étape `cargo audit` de `check-rust.yml` est `continue-on-error: true`. **La dérive `windows-sys` de #345 ne s'est pas déclenchée** : `rustls` est au cœur du sous-arbre TLS et n'a déplacé aucun des 7 pointeurs (diff vide sur toutes les lignes `windows-sys`, 6 versions identiques, 666 paquets avant/après, diff total 4 lignes) — la prémisse « tout bump du sous-arbre réseau/TLS » est trop large, le déclencheur tient à `hyper` ; mesure consignée en commentaire sur #345. Donc **pas de strophe écrite à la main** contrairement à #344 : cargo a écrit le lock, les checksums viennent de l'index. `rustls-webpki` 0.103.13 → 0.103.15 est **requis** et non du collatéral — 0.23.45 resserre sa borne de `^0.103.5` à `^0.103.14`. Chemin d'atteinte **plus étroit qu'`h2`** : `tauri-plugin-updater → reqwest 0.13.2` seulement, le `reqwest 0.12.28` que l'app déclare passant par `hyper-tls → native-tls → openssl`. **Pas de v0.15.1** (arbitrage de Max) : l'entrée reste sous `[Unreleased]`, l'exposition réelle étant quasi nulle — la transcription reste authentifiée, il faudrait que le serveur de release soit lui-même hostile.)
> >
> Précédente MAJ : 2026-09-12 (**v0.15.0 shippée** — tag `v0.15.0` sur `920dedd`, run 402 `build-and-release` vert en 22 min, 7 artefacts + `latest.json` vérifiés par script (version, `pub_date`, signatures non vides pour `linux-x86_64` et `windows-x86_64`, URLs sur la bonne version, notes extraites du CHANGELOG). Pré-vol local sur le tip avant de tagger : 111 Rust, 1181 vitest, build Vite, aucun worktree. Contenu : gating par édition (#297-#301), chantier import + migration v17 (#323-#331), fusion des catégories custom (#259), trois lots d'advisories (#310/#311/#343) ; `CLAUDE.md` remis à `0.15.0` (traînait à 0.6.3). **Prérequis testeurs réglé avant le tag** : v0.14.0 laissait `auto-update` ouvert en Free, v0.15.0 le ferme (Base+), donc une installation sans clé aurait reçu v0.15.0 comme dernière mise à jour automatique — deux clés **Premium 10 ans** émises le 2026-09-12 (Max + Michel Tremblay, compte Logto `michelt`) via `POST /v1/licenses/generate`, signature Ed25519 et claims vérifiés contre la clé publique embarquée du client ; la table `licenses` ne portait qu'une Base révoquée pour Max (achat Stripe de test remboursé le 07-19), et la révocation n'est jamais vue hors ligne. Détour infra : le VPS était **hors du tailnet depuis le 08-21** — clé de nœud Tailscale expirée (`peer's node key has expired`, aucun lien navigateur ne vient jamais dans ce cas), réparé par le Terminal Coolify (`tailscale up --ssh`) + *Disable key expiry* ; la clé du ThinkPad n'est pas autorisée sur l'IP publique `54.39.97.77`. **Étape 10 (`docs/qa-update-cycle.md`) pas encore déroulée** — à faire depuis les v0.14.0 installées avec clé Premium ; #320 : les `.rpm` ne se mettront pas à jour.) > Précédente MAJ : 2026-09-13 (**Étape 10 de v0.15.0 consignée** — Max rapporte que la mise à jour automatique v0.14.0 → v0.15.0 fonctionne chez les deux testeurs. Corroboration Forgejo : assets v0.15.0 à exactement 1 `.deb` + 1 `-setup.exe` téléchargés, 0 `.sig` — cohérent avec une machine Linux et une Windows passées par l'updater, sans en être la preuve ; invite polkit Linux non consignée ; `.rpm` hors test (#320). Trace rédigée pour le **corps** de la release (Forgejo n'a pas de commentaires de release ; le `PATCH` est une écriture externe bloquée en mode auto → remis à Max). **Le « code à 6 chiffres » reçu par courriel par un testeur n'est pas une étape de la mise à jour** : ni l'updater, ni la saisie de clé, ni l'activation machine n'en émettent — c'est le code de vérification Logto du bouton « Se connecter » de la carte Compte Maximus (Paramètres → Utilisateurs, juste au-dessus de la carte Licence), pour l'un de trois flux : connexion par courriel + code, mot de passe oublié, ou inscription ; MFA = TOTP seul. Le flux exact est dans le journal d'audit Logto (secret M2M sur le VPS seulement). Sans effet sur l'édition Premium. Cause probable : le texte de la carte dit « Le compte est requis uniquement pour les fonctionnalités Premium », faux depuis que Premium vient de la clé — à reformuler.)
## Position actuelle ## Position actuelle
@ -20,6 +20,7 @@ Audit critique de la page Bilan livré (`docs/audit-bilan-2026-05.md`, revue CPA
## Decisions recentes ## Decisions recentes
- 2026-09-21 : **RUSTSEC-2026-0285 (`rustls`) corrigée — l'audit quotidien rouge depuis 7 jours, et la prémisse de #345 restreinte par la mesure.** Trouvée au `/warmup` : `audit.yml` échouait tous les matins depuis le **2026-09-15** (runs 405→411), dernier vert le 404 du 09-14, sans aucun push sur `main` — le dernier commit était `97ee3c3` du 09-13. Une seule vulnérabilité : **RUSTSEC-2026-0285** / GHSA-2mjx-qc3c-rqvc, `rustls` 0.23.36, datée du **09-14**, CVSS 5.3 medium, vecteur `C:L/I:N/A:N`. L'advisory est parue **après** le tir du 09-14 à 06:00 UTC, d'où un premier rouge dès le lendemain — propagation plus rapide que les deux jours de `h2`. **Ce n'est pas un contournement** : rustls acceptait des messages de poignée de main TLS 1.3 au mauvais niveau de chiffrement lorsqu'ils suivaient un changement de clé dans le même enregistrement (un `EncryptedExtensions` en clair collé au `ServerHello`), là où la RFC 8446 §5.1 impose de fermer la connexion ; la transcription restant authentifiée, un attaquant en position réseau ne peut ni altérer ni compléter une poignée de main. **Atteignabilité par triple explicite**, jamais `--target all` : arbre identique sur les deux cibles livrées, mais **un seul chemin** — `tauri-plugin-updater → reqwest 0.13.2`, la mise à jour automatique. Plus étroit qu'`h2` en #343 : le `reqwest 0.12.28` déclaré en `Cargo.toml:43` (licence, auth, cours, rétroaction) passe par `hyper-tls → native-tls → openssl` et est **absent** de l'arbre inverse de `rustls`. Correctif publié + crate atteignable = **règle 1** de `.cargo/audit.toml` : corrigé, pas suppressé ; ni le fichier de suppressions ni le garde-fou (`CRATES="rsa"`) ne bougent. **Le piège attendu ne s'est pas produit, et c'est le résultat intéressant** : #345 annonce que tout bump atteignant le sous-arbre réseau/TLS fait redescendre les 7 pointeurs `windows-sys` de `0.61.2` — or `cargo update -p rustls --precise 0.23.45` n'en a déplacé aucun. Le déclencheur tient à `hyper` et à ce qu'il tire, pas à la couche TLS en général ; le corps de #345 est donc trop large et un commentaire l'y consigne, avec une suggestion : formuler le garde-fou sur la **version résolue de `windows-sys`** plutôt que sur une liste de crates déclencheurs — la liste est ce qui vient d'être trouvé faux, la version est ce qui est protégé. Conséquence pratique : aucune strophe écrite à la main, la sortie de cargo est conservée, donc les checksums viennent de l'index et non du rédacteur. `rustls-webpki` 0.103.13 → 0.103.15 l'accompagne et est **requis** : 0.23.45 resserre sa borne de `^0.103.5` à `^0.103.14`, même forme que le `http ^1 → ^1.1` relevé par la revue de #344, mais cette fois contraignante ; les quatre correctifs que #310 avait obtenus en posant 0.103.13 sont conservés. **Preuves plutôt que déductions** : contrôle sur l'ancien lock (exit 1 sur RUSTSEC-2026-0285) montrant que l'outil local reproduit le rouge de la CI ; `cargo metadata --locked` acceptant le lock sans le réécrire ; checksums vérifiés au `sha256sum` des `.crate`, et surtout les lignes `Downloaded`→`Compiling rustls v0.23.45` du run CI, qui prouvent l'archive réellement utilisée là où `cargo metadata` ne prouve que le graphe ; garde-fou tracé (`Suppression guard: 2 checks, exit 0`) ; **111 Rust** ; et `workflow_dispatch` d'`audit.yml` **sur `main` après merge** (run 414) plutôt que d'inférer le vert du run de PR. `/pr-review` **APPROVE** en re-prouvant depuis la source plutôt qu'en relayant — checksums authentifiés contre l'index crates.io, borne resserrée lue dans le champ `deps` de l'index, absence de `reqwest 0.12.28` de l'arbre inverse complet — et a levé une note fondée appliquée en `9abf474` : « aucun changement de comportement » valait pour le code de l'app, pas pour la couche TLS, où une poignée de main non conforme est désormais fermée. **Deux acquis de process** : (1) sur un événement `pull_request`, le filtre `paths` s'évalue contre le diff complet de la PR et non le delta du dernier commit — un commit markdown seul a donc rejoué `check-rust` (run 413), ce qui est la bonne sémantique ; (2) merge par l'API Forgejo (rebase ff, SHA inchangé) plutôt qu'en local, ce qui évite la réconciliation manuelle des PRs. **Pas de v0.15.1** : arbitrage de Max, l'entrée reste sous `[Unreleased]`. Aucune migration DB (v1→v17). (ref #348, #345, PR #349)
- 2026-09-13 : **Étape 10 de v0.15.0 consignée — le cycle de mise à jour réel passe, et le « code à 6 chiffres » reçu par un testeur n'est pas une étape de la mise à jour.** Max rapporte que v0.14.0 → v0.15.0 fonctionne chez les deux testeurs. Corroboration Forgejo au 09-13 : les assets v0.15.0 comptent exactement 1 `.deb` et 1 `-setup.exe` téléchargés, 0 `.sig` (l'updater lit les signatures dans `latest.json`) — cohérent avec une machine Linux et une Windows passées par l'updater, sans en être la preuve ; invite polkit Linux non consignée. Trace rédigée pour le **corps** de la release (Forgejo n'a pas de commentaires de release — `docs/qa-update-cycle.md` disait « commentaire », corrigé avec le `PATCH` à rejouer) ; le `PATCH` lui-même est une écriture externe bloquée par le classifier du mode auto → payload préparé, commande remise à Max en `!`. **Le code par courriel, retracé dans le code plutôt que deviné** : ni `tauri-plugin-updater`, ni la saisie de clé (validation Ed25519 locale), ni l'activation machine (`license_key` + `machine_id` ; côté `maximus-api`, seul `fulfillmentService` envoie un courriel) n'en émettent. La seule surface est **« Se connecter » de la carte Compte Maximus**, première carte de Paramètres → Utilisateurs, juste au-dessus de la carte Licence où un testeur vient saisir sa clé — PKCE dans le navigateur système → page Logto, dont le connecteur SMTP (Resend) envoie un code à 6 chiffres valide 10 min pour trois flux, d'après le `sign-in-exp` public du tenant : connexion par courriel + code (`verificationCode: true` sur l'identifiant email), « mot de passe oublié », inscription (`signUp.verify: true`) ; MFA = TOTP seul, donc exclu. Lequel des trois : seul le journal d'audit Logto le dit (console, ou `GET /api/logs?userId=` avec le secret M2M qui ne vit que sur le VPS). Sans effet sur l'édition : `current_entitlements` ne lit le compte que pour un abonnement `active`, sinon retombe sur la clé Premium. **Cause probable du clic** : le texte de la carte (« Le compte est requis uniquement pour les fonctionnalités Premium », hérité de v0.7) contredit v0.15.0 où Premium vient de la clé — piège pour tout testeur qui arrive avec une clé ; reformulation à ouvrir en issue. `logto-ops.md` enrichi (flux émetteurs du code + requête d'audit). **Suite dans la même séance** : évaluation pré-vente par 3 audits parallèles (desktop, API, site ; synthèse en mémoire `project-pre-sale-gaps`) — la chaîne Stripe est prouvée en mode test depuis le 07-19, ce qui bloque est de l'alignement : #270, Premium verrouillé sans offre vendable, identité vendeur fictive (CGV + `merchantInfo.ts`), copie produit « gratuit/bêta » contradictoire, mode live (#62/#141), backup Postgres des licences non prouvé ; puis `/analyze 270` → corps réécrit et 3 issues ouvertes (#346, #347, la-compagnie-maximus#153). Aucune migration DB (v1→v17). (ref v0.15.0, #320, #270, #346, #347) - 2026-09-13 : **Étape 10 de v0.15.0 consignée — le cycle de mise à jour réel passe, et le « code à 6 chiffres » reçu par un testeur n'est pas une étape de la mise à jour.** Max rapporte que v0.14.0 → v0.15.0 fonctionne chez les deux testeurs. Corroboration Forgejo au 09-13 : les assets v0.15.0 comptent exactement 1 `.deb` et 1 `-setup.exe` téléchargés, 0 `.sig` (l'updater lit les signatures dans `latest.json`) — cohérent avec une machine Linux et une Windows passées par l'updater, sans en être la preuve ; invite polkit Linux non consignée. Trace rédigée pour le **corps** de la release (Forgejo n'a pas de commentaires de release — `docs/qa-update-cycle.md` disait « commentaire », corrigé avec le `PATCH` à rejouer) ; le `PATCH` lui-même est une écriture externe bloquée par le classifier du mode auto → payload préparé, commande remise à Max en `!`. **Le code par courriel, retracé dans le code plutôt que deviné** : ni `tauri-plugin-updater`, ni la saisie de clé (validation Ed25519 locale), ni l'activation machine (`license_key` + `machine_id` ; côté `maximus-api`, seul `fulfillmentService` envoie un courriel) n'en émettent. La seule surface est **« Se connecter » de la carte Compte Maximus**, première carte de Paramètres → Utilisateurs, juste au-dessus de la carte Licence où un testeur vient saisir sa clé — PKCE dans le navigateur système → page Logto, dont le connecteur SMTP (Resend) envoie un code à 6 chiffres valide 10 min pour trois flux, d'après le `sign-in-exp` public du tenant : connexion par courriel + code (`verificationCode: true` sur l'identifiant email), « mot de passe oublié », inscription (`signUp.verify: true`) ; MFA = TOTP seul, donc exclu. Lequel des trois : seul le journal d'audit Logto le dit (console, ou `GET /api/logs?userId=` avec le secret M2M qui ne vit que sur le VPS). Sans effet sur l'édition : `current_entitlements` ne lit le compte que pour un abonnement `active`, sinon retombe sur la clé Premium. **Cause probable du clic** : le texte de la carte (« Le compte est requis uniquement pour les fonctionnalités Premium », hérité de v0.7) contredit v0.15.0 où Premium vient de la clé — piège pour tout testeur qui arrive avec une clé ; reformulation à ouvrir en issue. `logto-ops.md` enrichi (flux émetteurs du code + requête d'audit). **Suite dans la même séance** : évaluation pré-vente par 3 audits parallèles (desktop, API, site ; synthèse en mémoire `project-pre-sale-gaps`) — la chaîne Stripe est prouvée en mode test depuis le 07-19, ce qui bloque est de l'alignement : #270, Premium verrouillé sans offre vendable, identité vendeur fictive (CGV + `merchantInfo.ts`), copie produit « gratuit/bêta » contradictoire, mode live (#62/#141), backup Postgres des licences non prouvé ; puis `/analyze 270` → corps réécrit et 3 issues ouvertes (#346, #347, la-compagnie-maximus#153). Aucune migration DB (v1→v17). (ref v0.15.0, #320, #270, #346, #347)
- 2026-09-12 : **v0.15.0 taggée et publiée (`920dedd`, run 402 vert) — et deux clés Premium émises juste avant, parce que la release ferme la porte derrière elle.** Point de départ : Max demande comment les testeurs (lui et Michel) gardent l'accès une fois le gating actif. Lecture du code plutôt que de la spec : en v0.14.0 `FEATURE_TIERS` laisse `auto-update` **ouvert en Free** (« temporarily open until the license server is live »), v0.15.0 le passe Base+ — donc une installation sans clé reçoit v0.15.0 automatiquement et **plus jamais rien ensuite**, et se retrouve Free : profil actif seul, Budget/Ajustements/rapports avancés/Bilan verrouillés. Ni clause de grand-père ni période de grâce dans le code, `SR_DEV_EDITION` compilé hors des builds publiés. Réponse : une clé par testeur, saisie **avant** le tag (la carte Licence existe en v0.14.0 et la clé publique est identique entre v0.14.0 et `main`), validation locale sans réseau ; l'activation machine n'est requise que pour les cours du Bilan (`activation.token` en Bearer vers `/v1/prices`), et l'ancien chemin `/licenses/activate` du binaire passe par un 308 vers `/v1/` avec produit inféré du préfixe `SR-` (#48, vérifié en prod). Émission via `POST /v1/licenses/generate` (clé admin de Coolify déposée en `~/.maximus-admin-key`, bearer passé par process substitution, clés écrites en 600 hors transcription), puis **vérification indépendante** : signature Ed25519 contre la clé publique embarquée, `exp`+`iat` exigés par `strict_validation()`, préfixe/édition cohérents, expiration 2036-09-09. **Le détour qui a coûté la séance** : le VPS répondait `Connection timed out` sans lien de réauth — pas une attente navigateur mais **`peer's node key has expired`** (Tailscale, 180 j, `last seen 16d`), invisible depuis la console admin et sans réparation à distance ; l'IP publique `54.39.97.77` a le port 22 ouvert mais refuse la clé du ThinkPad, réparé par Max via le Terminal Coolify + *Disable key expiry*. Le classifier du mode auto bloque tout `ssh` vers le VPS (Tailscale et IP publique) → commandes remises à Max en `!`. Lecture Logto : 4 comptes, Michel = `michelt` (04-14), les deux autres sont le second compte de Max et une reliquat de La Suite Booking. Table `licenses` : une seule vraie clé, la Base de Max du 07-19, **révoquée** (Stripe test + remboursement) — et le client ne voit jamais `is_revoked` hors ligne, seul le serveur (activation, cours) la refuse. Release : pré-vol vert (111 Rust, 1181 vitest), 5 manifestes + 2 changelogs + ligne `CLAUDE.md` 0.6.3→0.15.0, tag annoté confirmé par Max avant push (publie `latest.json` aux installations existantes), run 402 en 22 min, 7 artefacts et `latest.json` vérifiés par script. Étape 10 à dérouler. (ref v0.15.0, #297-#301, #323-#331, #259, #310, #311, #343) - 2026-09-12 : **v0.15.0 taggée et publiée (`920dedd`, run 402 vert) — et deux clés Premium émises juste avant, parce que la release ferme la porte derrière elle.** Point de départ : Max demande comment les testeurs (lui et Michel) gardent l'accès une fois le gating actif. Lecture du code plutôt que de la spec : en v0.14.0 `FEATURE_TIERS` laisse `auto-update` **ouvert en Free** (« temporarily open until the license server is live »), v0.15.0 le passe Base+ — donc une installation sans clé reçoit v0.15.0 automatiquement et **plus jamais rien ensuite**, et se retrouve Free : profil actif seul, Budget/Ajustements/rapports avancés/Bilan verrouillés. Ni clause de grand-père ni période de grâce dans le code, `SR_DEV_EDITION` compilé hors des builds publiés. Réponse : une clé par testeur, saisie **avant** le tag (la carte Licence existe en v0.14.0 et la clé publique est identique entre v0.14.0 et `main`), validation locale sans réseau ; l'activation machine n'est requise que pour les cours du Bilan (`activation.token` en Bearer vers `/v1/prices`), et l'ancien chemin `/licenses/activate` du binaire passe par un 308 vers `/v1/` avec produit inféré du préfixe `SR-` (#48, vérifié en prod). Émission via `POST /v1/licenses/generate` (clé admin de Coolify déposée en `~/.maximus-admin-key`, bearer passé par process substitution, clés écrites en 600 hors transcription), puis **vérification indépendante** : signature Ed25519 contre la clé publique embarquée, `exp`+`iat` exigés par `strict_validation()`, préfixe/édition cohérents, expiration 2036-09-09. **Le détour qui a coûté la séance** : le VPS répondait `Connection timed out` sans lien de réauth — pas une attente navigateur mais **`peer's node key has expired`** (Tailscale, 180 j, `last seen 16d`), invisible depuis la console admin et sans réparation à distance ; l'IP publique `54.39.97.77` a le port 22 ouvert mais refuse la clé du ThinkPad, réparé par Max via le Terminal Coolify + *Disable key expiry*. Le classifier du mode auto bloque tout `ssh` vers le VPS (Tailscale et IP publique) → commandes remises à Max en `!`. Lecture Logto : 4 comptes, Michel = `michelt` (04-14), les deux autres sont le second compte de Max et une reliquat de La Suite Booking. Table `licenses` : une seule vraie clé, la Base de Max du 07-19, **révoquée** (Stripe test + remboursement) — et le client ne voit jamais `is_revoked` hors ligne, seul le serveur (activation, cours) la refuse. Release : pré-vol vert (111 Rust, 1181 vitest), 5 manifestes + 2 changelogs + ligne `CLAUDE.md` 0.6.3→0.15.0, tag annoté confirmé par Max avant push (publie `latest.json` aux installations existantes), run 402 en 22 min, 7 artefacts et `latest.json` vérifiés par script. Étape 10 à dérouler. (ref v0.15.0, #297-#301, #323-#331, #259, #310, #311, #343)
- 2026-09-04 : **#343 ouverte, #344 mergée — l'audit quotidien rouge depuis 17 jours, corrigé (`main` `5154c52`).** Découvert au `/warmup` : `audit.yml` échouait tous les matins depuis le **2026-08-19** (runs 375→391), dernier vert le 08-18, **aucun push sur `main` dans l'intervalle**. Une seule vulnérabilité : **RUSTSEC-2026-0258** / GHSA-q83h-524g-xf6h, `h2` 0.4.13, datée du 08-17 — DoS de sévérité faible, les frames HTTP/2 `DATA` vides sont acceptées et mises en file **sans aucune limite**, donc sur un flux non drainé la mémoire croît sans borne et la longueur peut déborder en panique. Le délai 08-17→08-19 est la propagation dans l'advisory-db. Ces 17 jours **sont** le mode d'échec que l'ADR 0018 visait à empêcher — « alarme permanente = alarme ignorée » — cette fois par le haut : le gate criait, personne ne regardait. **Atteignabilité par triple explicite**, jamais `--target all` : `h2` est sur les **deux** cibles livrées par deux chemins, `hyper`←`hyper-rustls`←`reqwest 0.13.2`←`tauri-plugin-updater` (le chemin de la mise à jour automatique) et `hyper-tls`←`reqwest 0.12.28` déclaré directement en `Cargo.toml:43` (licence, auth, cours, rétroaction — 4 modules). Correctif publié + crate atteignable = **règle 1** de `.cargo/audit.toml`, donc corrigé et non suppressé ; ni le fichier de suppressions ni le garde-fou de `check-rust.yml` ne bougent. **Le piège, et la raison d'un diff de 2 lignes** : `cargo update -p h2` donne bien 0.4.19 mais réaiguille au passage 7 pointeurs `windows-sys` (`colored`, `dirs-sys`, `errno`, `rustix`, `rustls-platform-verifier`, `tempfile`, `winapi-util`) de `0.61.2` vers 0.48/0.52/0.59 — **exactement ce que #321 avait défait délibérément le 08-15**. Laisser passer aurait régressé cette décision en silence, 3 semaines après. Le lock a donc été restauré et **seule la strophe `h2`** appliquée (version + checksum), après avoir **retracé depuis la source** — `git show v0.14.0:src-tauri/Cargo.lock`, les 7 crates comparés un à un — que `0.61.2` est bien l'état du tag, donc la seule configuration `windows-sys` dont on ait la preuve qu'elle compile sous Windows ; relayer la note de #321 n'aurait pas suffi. `cargo metadata --locked` accepte le résultat **sans le réécrire**. **`/pr-review` APPROVE et a trouvé ce que j'avais manqué** : une strophe écrite à la main ne garantit plus ni checksum ni liste de dépendances, et `h2` 0.4.19 **resserre sa borne `http` de `^1` à `^1.1`** — le genre de changement qui invalide un lock épinglé ; satisfait ici (`http 1.4.0`), checksum authentifié par `sha256sum` du `.crate`, et nuance utile qu'il pose : `cargo metadata --locked` prouve le graphe, **pas** le checksum — c'est le `Downloaded`→`Compiling` du run 392 qui le prouve. **Preuves plutôt que déductions** : contrôle sur l'ancien lock (exit 1 sur RUSTSEC-2026-0258) montrant que l'outil local reproduit le rouge de la CI ; log de la CI lu ligne à ligne parce que l'étape `cargo audit` de `check-rust` est `continue-on-error: true` et qu'un job vert n'y prouve rien ; garde-fou tracé (`Suppression guard: 2 checks, exit 0`) ; **111 Rust** ; et `workflow_dispatch` d'`audit.yml` **sur `main` après merge** (run 393, `Job succeeded`) plutôt que d'inférer le vert du run de PR. **Deux acquis au passage** : (1) le **glob `src-tauri/**` est enfin prouvé** — cette PR est le cas test isolé attendu depuis #232 (lock + 2 `.md` seulement) : `check-rust` a tourné, `check-frontend` correctement sauté par sa denylist ; le suivi peut être fermé ; (2) **#345 ouverte** — la contrainte `windows-sys 0.61.2` en est à sa 3e rencontre (#310 l'introduit, #321 la défait, #344 l'évite à la main) et ne vit dans **aucun** ADR, aucun commentaire de `Cargo.toml`, aucun garde CI : seulement 3 lignes de prose de STATE.md et un message de commit, donc le prochain `cargo update` la régressera en silence. Merge via l'API Forgejo (rebase ff, SHA inchangé) plutôt qu'en local, ce qui évite la réconciliation manuelle des PRs. Aucune migration DB (v1→v17). (ref #343, #345, PR #344) - 2026-09-04 : **#343 ouverte, #344 mergée — l'audit quotidien rouge depuis 17 jours, corrigé (`main` `5154c52`).** Découvert au `/warmup` : `audit.yml` échouait tous les matins depuis le **2026-08-19** (runs 375→391), dernier vert le 08-18, **aucun push sur `main` dans l'intervalle**. Une seule vulnérabilité : **RUSTSEC-2026-0258** / GHSA-q83h-524g-xf6h, `h2` 0.4.13, datée du 08-17 — DoS de sévérité faible, les frames HTTP/2 `DATA` vides sont acceptées et mises en file **sans aucune limite**, donc sur un flux non drainé la mémoire croît sans borne et la longueur peut déborder en panique. Le délai 08-17→08-19 est la propagation dans l'advisory-db. Ces 17 jours **sont** le mode d'échec que l'ADR 0018 visait à empêcher — « alarme permanente = alarme ignorée » — cette fois par le haut : le gate criait, personne ne regardait. **Atteignabilité par triple explicite**, jamais `--target all` : `h2` est sur les **deux** cibles livrées par deux chemins, `hyper`←`hyper-rustls`←`reqwest 0.13.2`←`tauri-plugin-updater` (le chemin de la mise à jour automatique) et `hyper-tls`←`reqwest 0.12.28` déclaré directement en `Cargo.toml:43` (licence, auth, cours, rétroaction — 4 modules). Correctif publié + crate atteignable = **règle 1** de `.cargo/audit.toml`, donc corrigé et non suppressé ; ni le fichier de suppressions ni le garde-fou de `check-rust.yml` ne bougent. **Le piège, et la raison d'un diff de 2 lignes** : `cargo update -p h2` donne bien 0.4.19 mais réaiguille au passage 7 pointeurs `windows-sys` (`colored`, `dirs-sys`, `errno`, `rustix`, `rustls-platform-verifier`, `tempfile`, `winapi-util`) de `0.61.2` vers 0.48/0.52/0.59 — **exactement ce que #321 avait défait délibérément le 08-15**. Laisser passer aurait régressé cette décision en silence, 3 semaines après. Le lock a donc été restauré et **seule la strophe `h2`** appliquée (version + checksum), après avoir **retracé depuis la source** — `git show v0.14.0:src-tauri/Cargo.lock`, les 7 crates comparés un à un — que `0.61.2` est bien l'état du tag, donc la seule configuration `windows-sys` dont on ait la preuve qu'elle compile sous Windows ; relayer la note de #321 n'aurait pas suffi. `cargo metadata --locked` accepte le résultat **sans le réécrire**. **`/pr-review` APPROVE et a trouvé ce que j'avais manqué** : une strophe écrite à la main ne garantit plus ni checksum ni liste de dépendances, et `h2` 0.4.19 **resserre sa borne `http` de `^1` à `^1.1`** — le genre de changement qui invalide un lock épinglé ; satisfait ici (`http 1.4.0`), checksum authentifié par `sha256sum` du `.crate`, et nuance utile qu'il pose : `cargo metadata --locked` prouve le graphe, **pas** le checksum — c'est le `Downloaded`→`Compiling` du run 392 qui le prouve. **Preuves plutôt que déductions** : contrôle sur l'ancien lock (exit 1 sur RUSTSEC-2026-0258) montrant que l'outil local reproduit le rouge de la CI ; log de la CI lu ligne à ligne parce que l'étape `cargo audit` de `check-rust` est `continue-on-error: true` et qu'un job vert n'y prouve rien ; garde-fou tracé (`Suppression guard: 2 checks, exit 0`) ; **111 Rust** ; et `workflow_dispatch` d'`audit.yml` **sur `main` après merge** (run 393, `Job succeeded`) plutôt que d'inférer le vert du run de PR. **Deux acquis au passage** : (1) le **glob `src-tauri/**` est enfin prouvé** — cette PR est le cas test isolé attendu depuis #232 (lock + 2 `.md` seulement) : `check-rust` a tourné, `check-frontend` correctement sauté par sa denylist ; le suivi peut être fermé ; (2) **#345 ouverte** — la contrainte `windows-sys 0.61.2` en est à sa 3e rencontre (#310 l'introduit, #321 la défait, #344 l'évite à la main) et ne vit dans **aucun** ADR, aucun commentaire de `Cargo.toml`, aucun garde CI : seulement 3 lignes de prose de STATE.md et un message de commit, donc le prochain `cargo update` la régressera en silence. Merge via l'API Forgejo (rebase ff, SHA inchangé) plutôt qu'en local, ce qui évite la réconciliation manuelle des PRs. Aucune migration DB (v1→v17). (ref #343, #345, PR #344)
@ -29,12 +30,11 @@ Audit critique de la page Bilan livré (`docs/audit-bilan-2026-05.md`, revue CPA
- 2026-07-27 : **#232 mergée — CI split, caches morts retirés, `cargo-audit` pré-buildé** (`main` `7779f7d`, PR #309 rebase, milestone `spec-ci-build-optimization` **3/4**). `/analyze` a confirmé la prémisse mais **révisé le cadrage sur 3 points mesurés** : (1) les 2 jobs ne tournent **pas en parallèle** — runner à capacité 1, `frontend` démarre à la seconde où `rust` finit sur tous les runs de l'historique → chaque PR payait rust+frontend ≈ 24,5 min ; (2) **3 des 40 derniers commits first-parent** touchent `src-tauri/`, dont 2 `chore: release` (push sur `main`, ne déclenche pas la CI) → le path-filter est le **gain dominant**, pas un effet de bord ; (3) le risque « required check skippé » est **nul** (`enable_status_check: false` sur la protection de `main`, vérifié API). Chrono re-mesuré du run 326 : **12m15s de gaspillage sur 21m44** — save `target/` 6m11 + save registry 43s (`reserveCache failed`, cause #234) + `cargo install cargo-audit` 4m41 + restores ~40s ; **nouveau vs baseline #231** : le restore **timeout aussi** désormais (`getCacheEntry failed`), le 30 juin n'avait qu'un MISS propre. Résultat : **rust 21m44 → 8m55** (−59 %), **frontend 2m28 → 1m43**, **PR frontend-only 24m12 → 1m43** (−93 %). **Trou de couverture fermé** : `branches: [main]` ne matchait pas une PR stackée → **#305-#308 (pile feature-gating) n'ont eu aucune CI**, seules #303/#304 (base `main`) ont tourné ; plus aucun filtre `branches:`. 2 écarts assumés vs le corps d'origine, tranchés sur mesure : cache npm retiré aussi (23s/run) et **denylist** pour le frontend (job à 2,5 min → le faire tourner pour rien est bon marché, ne PAS le faire tourner silencieusement ne l'est pas ; le job cher garde un allowlist strict). `/pr-review` **APPROVE** — a récupéré les logs CI plutôt que juger le diff, et levé que l'override `PATH` job-level aurait pu masquer le binaire d'`install-action` (vérifié : audit bien exécuté) ; 2 des 6 suggestions appliquées (`.claude/**` au denylist, ref `check.yml` périmée dans le skill `release`). **Skip prouvé empiriquement** : le 2e push (ni `src-tauri/**` ni `check-rust.yml`) n'a **pas** déclenché `check-rust`. **Reste non prouvé — le glob `src-tauri/**` lui-même** (seule l'entrée chemin-exact a matché) : mode d'échec **silencieux** sur la PR Rust (1/40, ni `cargo check` ni `cargo test`) → #310 (`cargo update` touchant `Cargo.lock` seul) sera le test isolé, à surveiller. Le retrait du `|| true` a **découvert 9 advisories RUSTSEC réelles** (préexistantes, seulement masquées) → **#310** ouverte : `rustls-webpki` ×4 + `tar` ×2 atteignables via `tauri-plugin-updater`/`reqwest` (correctifs **patch**), `quick-xml` ×2 bloqué en amont par `plist`←`tauri` (bump majeur), `rsa` sans correctif publié mais seul parent `sqlx-mysql` — absent de tout arbre de compilation (`cargo tree -i rsa --target all` vide, projet SQLite) donc vraisemblablement **inatteignable**. Conséquence : `audit.yml` (quotidien, bloquant par choix) sera **rouge dès son 1er run** tant que #310 n'est pas traitée — alarme permanente = alarme ignorée, donc à lander tôt. Aucune migration DB (v1→v16). (ref #232, PR #309, #310) - 2026-07-27 : **#232 mergée — CI split, caches morts retirés, `cargo-audit` pré-buildé** (`main` `7779f7d`, PR #309 rebase, milestone `spec-ci-build-optimization` **3/4**). `/analyze` a confirmé la prémisse mais **révisé le cadrage sur 3 points mesurés** : (1) les 2 jobs ne tournent **pas en parallèle** — runner à capacité 1, `frontend` démarre à la seconde où `rust` finit sur tous les runs de l'historique → chaque PR payait rust+frontend ≈ 24,5 min ; (2) **3 des 40 derniers commits first-parent** touchent `src-tauri/`, dont 2 `chore: release` (push sur `main`, ne déclenche pas la CI) → le path-filter est le **gain dominant**, pas un effet de bord ; (3) le risque « required check skippé » est **nul** (`enable_status_check: false` sur la protection de `main`, vérifié API). Chrono re-mesuré du run 326 : **12m15s de gaspillage sur 21m44** — save `target/` 6m11 + save registry 43s (`reserveCache failed`, cause #234) + `cargo install cargo-audit` 4m41 + restores ~40s ; **nouveau vs baseline #231** : le restore **timeout aussi** désormais (`getCacheEntry failed`), le 30 juin n'avait qu'un MISS propre. Résultat : **rust 21m44 → 8m55** (−59 %), **frontend 2m28 → 1m43**, **PR frontend-only 24m12 → 1m43** (−93 %). **Trou de couverture fermé** : `branches: [main]` ne matchait pas une PR stackée → **#305-#308 (pile feature-gating) n'ont eu aucune CI**, seules #303/#304 (base `main`) ont tourné ; plus aucun filtre `branches:`. 2 écarts assumés vs le corps d'origine, tranchés sur mesure : cache npm retiré aussi (23s/run) et **denylist** pour le frontend (job à 2,5 min → le faire tourner pour rien est bon marché, ne PAS le faire tourner silencieusement ne l'est pas ; le job cher garde un allowlist strict). `/pr-review` **APPROVE** — a récupéré les logs CI plutôt que juger le diff, et levé que l'override `PATH` job-level aurait pu masquer le binaire d'`install-action` (vérifié : audit bien exécuté) ; 2 des 6 suggestions appliquées (`.claude/**` au denylist, ref `check.yml` périmée dans le skill `release`). **Skip prouvé empiriquement** : le 2e push (ni `src-tauri/**` ni `check-rust.yml`) n'a **pas** déclenché `check-rust`. **Reste non prouvé — le glob `src-tauri/**` lui-même** (seule l'entrée chemin-exact a matché) : mode d'échec **silencieux** sur la PR Rust (1/40, ni `cargo check` ni `cargo test`) → #310 (`cargo update` touchant `Cargo.lock` seul) sera le test isolé, à surveiller. Le retrait du `|| true` a **découvert 9 advisories RUSTSEC réelles** (préexistantes, seulement masquées) → **#310** ouverte : `rustls-webpki` ×4 + `tar` ×2 atteignables via `tauri-plugin-updater`/`reqwest` (correctifs **patch**), `quick-xml` ×2 bloqué en amont par `plist`←`tauri` (bump majeur), `rsa` sans correctif publié mais seul parent `sqlx-mysql` — absent de tout arbre de compilation (`cargo tree -i rsa --target all` vide, projet SQLite) donc vraisemblablement **inatteignable**. Conséquence : `audit.yml` (quotidien, bloquant par choix) sera **rouge dès son 1er run** tant que #310 n'est pas traitée — alarme permanente = alarme ignorée, donc à lander tôt. Aucune migration DB (v1→v16). (ref #232, PR #309, #310)
- 2026-07-21 : **Milestone `planned-2026-07-19-feature-gating` livrée 6/6 et fermée — le gating par tier est sur `main`** ([Unreleased], candidate v0.15.0). Reprise du run `/autopilot` interrompu le 19 au soir (mort après la PR #303 ; worker #298 tué sans commit — 2 worktrees + branche vide nettoyés). Séquence : `/pr-review` #303 **REQUEST_CHANGES** (le retry backoff CWE-703 s'armait aussi sur un échec de `submitKey` → l'auto-refresh effaçait le message « clé invalide » ~1 s après ; et `status: "error"` persistant aurait laissé `ready=false` à jamais pour les futurs RequireFeature) → fix `b9e13b5` : validation de clé **orthogonale** au lifecycle de chargement (`validationError` dédié, `status` intouché, reducer exporté + 6 tests) → **APPROVE** → merge API. Relance `/autopilot` en session : **5 workers séquentiels en pile linéaire** (#302 dépend de tout ; CHANGELOG centralisé dans #302 → zéro conflit inter-maillons) → PRs #304-#308, 0 needs-clarification, 0 blocked ([rapport](reports/DAILY-REPORT-2026-07-20.md)). `/pr-review` **×5 parallèles → APPROVE ×5** ; seule retouche user-facing : coquille FR `docs.editions` (« tout la » → « tout de la », `6de9617`). Merge : tip cumulé validé (871 vitest + build + cargo 106) puis **ff-only** → push `main` **refusé : branche protégée** (whitelist `maximus`, règle du 2026-03-07, jamais rencontrée avant) — cause réelle : `~/.git-credentials` porte 3 identités Forgejo et `defenseur-auto-bot` en 1re position **shadow** `maximus` (git prend la 1re entrée du host ; les pushes de branches passaient, seule `main` whitelistée refusait). Fix scopé : `git remote set-url origin https://maximus@…` (ne PAS réordonner le fichier partagé — les agents defenseurs s'authentifient par lui). Push OK → 5 issues auto-fermées (`Resolves #N`), 5 PRs fermées + commentées, milestone 6/6 fermée, branches supprimées (+ purge de 7 branches `worktree-agent-*` du harness ; le « résidu `origin/issue-259` » signalé au warmup était un tracking ref périmé faute de `fetch --prune` — la branche remote avait bien été supprimée le 19). Décisions workers notables : tuiles `ProfileSelectionPage` **non** verrouillées (page atteinte seulement sans profil actif résoluble → un verrou heuristique risquerait un soft-lock hors de tous les profils) ; dev-override en **tête** de résolution (sinon un compte Premium actif masquerait `SR_DEV_EDITION`) ; `#[serde(default)]` préexistant sur `features` = les licences Base déjà émises sans le champ ne régressent pas. Gotcha process : un fork `/pr-review` lancé pendant que la CI est `pending` se termine « en attente du monitor CI » **sans jamais poster** (rien ne survit au fork) → monitorer la CI depuis le main loop, relancer le skill après le vert. Post-merge non bloquant : extraction `LockBadge`/`UpsellPanel` si une 3e surface apparaît ; label PIN « (annuler) » (bug cosmétique préexistant) ; job CI ponctuel `--features dev-override`. ADR 0017 Accepted. Aucune migration DB (v1→v16). (ref planned-2026-07-19-feature-gating, #297-#302, PRs #303-#308) - 2026-07-21 : **Milestone `planned-2026-07-19-feature-gating` livrée 6/6 et fermée — le gating par tier est sur `main`** ([Unreleased], candidate v0.15.0). Reprise du run `/autopilot` interrompu le 19 au soir (mort après la PR #303 ; worker #298 tué sans commit — 2 worktrees + branche vide nettoyés). Séquence : `/pr-review` #303 **REQUEST_CHANGES** (le retry backoff CWE-703 s'armait aussi sur un échec de `submitKey` → l'auto-refresh effaçait le message « clé invalide » ~1 s après ; et `status: "error"` persistant aurait laissé `ready=false` à jamais pour les futurs RequireFeature) → fix `b9e13b5` : validation de clé **orthogonale** au lifecycle de chargement (`validationError` dédié, `status` intouché, reducer exporté + 6 tests) → **APPROVE** → merge API. Relance `/autopilot` en session : **5 workers séquentiels en pile linéaire** (#302 dépend de tout ; CHANGELOG centralisé dans #302 → zéro conflit inter-maillons) → PRs #304-#308, 0 needs-clarification, 0 blocked ([rapport](reports/DAILY-REPORT-2026-07-20.md)). `/pr-review` **×5 parallèles → APPROVE ×5** ; seule retouche user-facing : coquille FR `docs.editions` (« tout la » → « tout de la », `6de9617`). Merge : tip cumulé validé (871 vitest + build + cargo 106) puis **ff-only** → push `main` **refusé : branche protégée** (whitelist `maximus`, règle du 2026-03-07, jamais rencontrée avant) — cause réelle : `~/.git-credentials` porte 3 identités Forgejo et `defenseur-auto-bot` en 1re position **shadow** `maximus` (git prend la 1re entrée du host ; les pushes de branches passaient, seule `main` whitelistée refusait). Fix scopé : `git remote set-url origin https://maximus@…` (ne PAS réordonner le fichier partagé — les agents defenseurs s'authentifient par lui). Push OK → 5 issues auto-fermées (`Resolves #N`), 5 PRs fermées + commentées, milestone 6/6 fermée, branches supprimées (+ purge de 7 branches `worktree-agent-*` du harness ; le « résidu `origin/issue-259` » signalé au warmup était un tracking ref périmé faute de `fetch --prune` — la branche remote avait bien été supprimée le 19). Décisions workers notables : tuiles `ProfileSelectionPage` **non** verrouillées (page atteinte seulement sans profil actif résoluble → un verrou heuristique risquerait un soft-lock hors de tous les profils) ; dev-override en **tête** de résolution (sinon un compte Premium actif masquerait `SR_DEV_EDITION`) ; `#[serde(default)]` préexistant sur `features` = les licences Base déjà émises sans le champ ne régressent pas. Gotcha process : un fork `/pr-review` lancé pendant que la CI est `pending` se termine « en attente du monitor CI » **sans jamais poster** (rien ne survit au fork) → monitorer la CI depuis le main loop, relancer le skill après le vert. Post-merge non bloquant : extraction `LockBadge`/`UpsellPanel` si une 3e surface apparaît ; label PIN « (annuler) » (bug cosmétique préexistant) ; job CI ponctuel `--features dev-override`. ADR 0017 Accepted. Aucune migration DB (v1→v16). (ref planned-2026-07-19-feature-gating, #297-#302, PRs #303-#308)
- 2026-07-19 : **Chantier gating par tier planifié via `/spec` → milestone `spec-feature-gating` (#297-#302)**. Origine : réflexion monétisation de Max (« changer le périmètre des abonnements »). Matrice tranchée : **Free** = Dashboard/Import/Transactions/Catégories/**rapport Tendance**/Export chiffré/Changelog (mono-profil) ; **Base** = + les 4 autres rapports/Budget/multi-profils/auto-update ; **Premium** = + Bilan complet (patrimoine + cours). Admin (Max) = licence Premium auto-émise (pas de 4e édition). Décisions clés : upsell **verrouillé** (pas masqué) ; durcissement **sec non destructif** (blocage d'accès, données conservées, récupérables par upgrade) ; entitlements **statique + override `features[]`** ; **enforcement UI-only** (soft-paywall GPL assumé, seul le gate cours reste dur/server-enforced) ; **#271 absorbé** (auto-update Base+, fermé superseded). Re-ancrage Phase 3b : socle déjà là (3 éditions Ed25519, `current_edition`, `check_entitlement`) mais `is_feature_allowed` ignore `features[]` + `useLicense` per-appel → **LicenseProvider** requis pour un `useEntitlement` sync ; rapports = routes distinctes (gate par route) ; #271 = 1 ligne. 6 issues (socle→garde UI→routes/sidebar→multi-profils→#271 Rust→docs), specs force-add (gitignorées, précédent #295), aucune migration DB. **`/review-spec` 3 experts → verdict 🟡, tout corrigé dans le plan** : 2 🔴 (nav `reports` gaté à tort alors que hub Free ; `advanced-reports` Rust mort contredit `reports-advanced`) + 6 🟡 (override `features[]` **fail-closed en Free** CWE-863 : une clé copiée downgrade free mais expose ses features signées ; `SR_DEV_EDITION` derrière une Cargo feature `dev-override` pas `debug_assertions` CWE-489 ; LicenseProvider récup. d'erreur CWE-703 ; `useEntitlement` → `{allowed,ready}` anti-flash ; `useIsPremium.test` à migrer ; gate création profil dans `ProfileFormModal`, point unique). Ajustements tranché **Base** par Max. **Re-homée `planned-2026-07-19-feature-gating`** (`/plan-run` Step 0 : bodies rendus auto-suffisants ; résiduel drainé — CTA « Obtenir » désactivé + « bientôt », dev-override gardé). Prête `/autopilot`. (ref planned-2026-07-19-feature-gating, #297-#302) - 2026-07-19 : **Chantier gating par tier planifié via `/spec` → milestone `spec-feature-gating` (#297-#302)**. Origine : réflexion monétisation de Max (« changer le périmètre des abonnements »). Matrice tranchée : **Free** = Dashboard/Import/Transactions/Catégories/**rapport Tendance**/Export chiffré/Changelog (mono-profil) ; **Base** = + les 4 autres rapports/Budget/multi-profils/auto-update ; **Premium** = + Bilan complet (patrimoine + cours). Admin (Max) = licence Premium auto-émise (pas de 4e édition). Décisions clés : upsell **verrouillé** (pas masqué) ; durcissement **sec non destructif** (blocage d'accès, données conservées, récupérables par upgrade) ; entitlements **statique + override `features[]`** ; **enforcement UI-only** (soft-paywall GPL assumé, seul le gate cours reste dur/server-enforced) ; **#271 absorbé** (auto-update Base+, fermé superseded). Re-ancrage Phase 3b : socle déjà là (3 éditions Ed25519, `current_edition`, `check_entitlement`) mais `is_feature_allowed` ignore `features[]` + `useLicense` per-appel → **LicenseProvider** requis pour un `useEntitlement` sync ; rapports = routes distinctes (gate par route) ; #271 = 1 ligne. 6 issues (socle→garde UI→routes/sidebar→multi-profils→#271 Rust→docs), specs force-add (gitignorées, précédent #295), aucune migration DB. **`/review-spec` 3 experts → verdict 🟡, tout corrigé dans le plan** : 2 🔴 (nav `reports` gaté à tort alors que hub Free ; `advanced-reports` Rust mort contredit `reports-advanced`) + 6 🟡 (override `features[]` **fail-closed en Free** CWE-863 : une clé copiée downgrade free mais expose ses features signées ; `SR_DEV_EDITION` derrière une Cargo feature `dev-override` pas `debug_assertions` CWE-489 ; LicenseProvider récup. d'erreur CWE-703 ; `useEntitlement` → `{allowed,ready}` anti-flash ; `useIsPremium.test` à migrer ; gate création profil dans `ProfileFormModal`, point unique). Ajustements tranché **Base** par Max. **Re-homée `planned-2026-07-19-feature-gating`** (`/plan-run` Step 0 : bodies rendus auto-suffisants ; résiduel drainé — CTA « Obtenir » désactivé + « bientôt », dev-override gardé). Prête `/autopilot`. (ref planned-2026-07-19-feature-gating, #297-#302)
- 2026-07-18 : **Epic #260 « rapports uniformes » fermée + #259 livrée en PR #296 (review)**. #260 fermée après `/analyze` : **4/5 rapports pleinement conformes** (income-statement + filtres partagés + collapse par-profil) ; les 3 écarts restants vs le texte de l'epic sont des **décisions de conception assumées**, entérinés par Max — (1) lignes vides budget non masquées (spec décision 6 : grille = surface d'édition) ; (2) collapse budget replié « comme partout » (#289/ADR 0016 inverse le « sauf budget » de l'epic) ; (3) dashboard convergé sur le modèle Cartes plutôt qu'une table income-statement hiérarchique (décision 5). **Correction STATE** : les entrées 07-08/07-11 « reste de #260 = #259 » sont fausses — #259 est une migration de taxonomie sans lien avec l'epic (recadrée 07-12), et les vraies déviations #260 n'y étaient pas tracées. **#259** (fusion des catégories custom) livrée en PR #296 (`issue-259-merge-custom-categories`) : bloc préservé de `StepSimulate` rendu en `MappingRow`, reducer `RESOLVE_ROW` résout rows+preserved (`unresolved` compté sur seed only → ne bloque pas « Suivant »), writer via helper `isResolvedTarget` (fourre-tout créé seulement s'il reste une custom non fusionnée ; customs fusionnées désactivées au lieu d'être re-parentées). Plan-check pass (6 MINOR, 3 intégrés), **836 vitest** + build tsc/vite propres, régression parent/enfant custom couverte, aucune migration DB. `/pr-review` **APPROVE**, mergée (rebase) le 2026-07-19 → `main` `2314a64`, #259 fermée, branche supprimée. (ref #260, #259 PR #296)
## Blockers actifs ## Blockers actifs
- Aucun blocker externe dur. `spec-monetisation` **fermée 12/12** (2026-07-08) — #50/#52/#53/#135/#136 livrées, ne sont plus des blockers. - Aucun blocker externe dur. `spec-monetisation` **fermée 12/12** (2026-07-08) — #50/#52/#53/#135/#136 livrées, ne sont plus des blockers.
- Backlog ouvert (`status:ready`, non bloqué) : `spec-paiements` **4 issues ouvertes après l'analyse du 2026-09-13** : #270 (activation /v1 + product + URL localisée, périmètre strict ; un défaut de plus trouvé sur les mêmes lignes : l'enveloppe d'erreur `/v1` `{error:{code,message}}` est lue comme une chaîne, donc tout refus serveur s'affiche « Activation failed »), #346 (CTA « Obtenir Base » des UpsellGate + lien d'achat carte MAJ + texte carte Compte, dépend de #270 ; « Obtenir Premium » reste désactivé faute d'offre vendable), #347 (erreurs de licence en anglais brut, clé expirée/révoquée muette, limite de machines jamais affichée), et côté site maximus/la-compagnie-maximus#153 (téléchargement direct Windows depuis la page produit + redirection de l'ancienne URL `/simpl-resultat`) ; go-live = la-compagnie-maximus#141 + maximus-api#62 ; #271 absorbé par #301, livré ; `spec-ci-build-optimization` **3/4** (#232 livrée le 2026-07-27 ; reste **#234** connectivité du serveur de cache runner, qui débloquerait `Swatinem/rust-cache` — accès hôte VPS requis). Le suivi « glob `src-tauri/**` non prouvé » ouvert avec #232 est **levé** : la PR #344 est le cas test isolé attendu (`Cargo.lock` + 2 `.md` seulement) — `check-rust` a bien tourné, `check-frontend` a bien été sauté par sa denylist. - Backlog ouvert (`status:ready`, non bloqué) : `spec-paiements` **4 issues ouvertes après l'analyse du 2026-09-13** : #270 (activation /v1 + product + URL localisée, périmètre strict ; un défaut de plus trouvé sur les mêmes lignes : l'enveloppe d'erreur `/v1` `{error:{code,message}}` est lue comme une chaîne, donc tout refus serveur s'affiche « Activation failed »), #346 (CTA « Obtenir Base » des UpsellGate + lien d'achat carte MAJ + texte carte Compte, dépend de #270 ; « Obtenir Premium » reste désactivé faute d'offre vendable), #347 (erreurs de licence en anglais brut, clé expirée/révoquée muette, limite de machines jamais affichée), et côté site maximus/la-compagnie-maximus#153 (téléchargement direct Windows depuis la page produit + redirection de l'ancienne URL `/simpl-resultat`) ; go-live = la-compagnie-maximus#141 + maximus-api#62 ; #271 absorbé par #301, livré ; `spec-ci-build-optimization` **3/4** (#232 livrée le 2026-07-27 ; reste **#234** connectivité du serveur de cache runner, qui débloquerait `Swatinem/rust-cache` — accès hôte VPS requis). Le suivi « glob `src-tauri/**` non prouvé » ouvert avec #232 est **levé** : la PR #344 est le cas test isolé attendu (`Cargo.lock` + 2 `.md` seulement) — `check-rust` a bien tourné, `check-frontend` a bien été sauté par sa denylist.
- Suivis des advisories : **#312, #313 et #315 fermées le 2026-08-15** (PRs #321/#322 mergées). **#314 fermée le 2026-08-15, prémisse infirmée** — elle affirmait « zéro run `schedule` », or `audit.yml` tire tous les jours à 06:00 UTC (**19 runs `schedule` verts** du 07-28 au 08-15, sans trou) ; elle a été écrite le 07-27 vers 23:56Z, ~6 h avant le premier tir programmé possible, et décrivait donc une fenêtre trop courte, pas un scheduler en panne. Ces 19 runs confirment au passage que `.cargo/audit.toml` est bien lu en conteneur **sur `main`** — l'ADR 0018 ne l'avait vérifié que par un `workflow_dispatch` sur une branche. **#343 fermée le 2026-09-04** (PR #344) — RUSTSEC-2026-0258 sur `h2`, la seule vulnérabilité qui rougissait l'audit depuis le 08-19 ; l'audit est de nouveau vert sur `main`, prouvé par `workflow_dispatch` (run 393) et non déduit du run de PR. Restent **#317** (re-évaluer `react-router` à une migration v8), **#319** (déclencheur de retrait pour `rsa`, désormais **seule** entrée de `.cargo/audit.toml` — le garde-fou est passé à 2 vérifications) et **#345** (domicilier la contrainte `windows-sys 0.61.2` — analysée le 09-04 : le corps d'origine, écrit de mémoire en fin de revue, se trompait sur trois points. Le déclencheur n'est pas « n'importe quel `cargo update` » mais tout bump atteignant le sous-arbre réseau/TLS — mesuré : `-p anyhow` ne bouge rien, `-p hyper` fait descendre les 7 — soit précisément là où tombent les advisories. #310 avait **bien** vu la dérive et l'avait jugée inoffensive *sur Linux*, où elle l'est ; son coût tombe sur le build Windows que personne ne mesurait : **3 versions de `windows-sys` compilées contre 5**. Aucun risque de correction — les 7 crates déclarent des bornes larges couvrant les deux états. Et cargo annonce « Locking 1 package » en écrivant 8 changements, donc le silence est le sien. Décision : garde-fou CI sur la version + ADR portant la condition de sortie. Le check croisé Windows depuis Linux a été **testé et écarté** — `cc-rs` s'arrête sur `lib.exe`, il faudrait `cargo-xwin`). - Suivis des advisories : **#312, #313 et #315 fermées le 2026-08-15** (PRs #321/#322 mergées). **#314 fermée le 2026-08-15, prémisse infirmée** — elle affirmait « zéro run `schedule` », or `audit.yml` tire tous les jours à 06:00 UTC (**19 runs `schedule` verts** du 07-28 au 08-15, sans trou) ; elle a été écrite le 07-27 vers 23:56Z, ~6 h avant le premier tir programmé possible, et décrivait donc une fenêtre trop courte, pas un scheduler en panne. Ces 19 runs confirment au passage que `.cargo/audit.toml` est bien lu en conteneur **sur `main`** — l'ADR 0018 ne l'avait vérifié que par un `workflow_dispatch` sur une branche. **#343 fermée le 2026-09-04** (PR #344) — RUSTSEC-2026-0258 sur `h2`, la seule vulnérabilité qui rougissait l'audit depuis le 08-19 ; l'audit est de nouveau vert sur `main`, prouvé par `workflow_dispatch` (run 393) et non déduit du run de PR. **#348 fermée le 2026-09-21** (PR #349) — RUSTSEC-2026-0285 sur `rustls`, seule vulnérabilité rougissant l'audit depuis le 09-15 (7 runs, 405→411) ; vert reprouvé par `workflow_dispatch` sur `main` (run 414 : 666 paquets, zéro vulnérabilité, 12 warnings autorisés). Deux advisories en cinq semaines sur le même chemin, celui de la mise à jour automatique. Restent **#317** (re-évaluer `react-router` à une migration v8), **#319** (déclencheur de retrait pour `rsa`, désormais **seule** entrée de `.cargo/audit.toml` — le garde-fou est passé à 2 vérifications) et **#345** (domicilier la contrainte `windows-sys 0.61.2` — analysée le 09-04 : le corps d'origine, écrit de mémoire en fin de revue, se trompait sur trois points. Le déclencheur n'est pas « n'importe quel `cargo update` » mais tout bump atteignant le sous-arbre réseau/TLS — mesuré : `-p anyhow` ne bouge rien, `-p hyper` fait descendre les 7 — soit précisément là où tombent les advisories. **Prémisse restreinte le 2026-09-21 par #348** : `-p rustls`, au cœur de ce même sous-arbre, n'en déplace **aucun** — le déclencheur tient à `hyper` et à ce qu'il tire, pas à la couche TLS en général, donc « sous-arbre réseau/TLS » prédit une dérive là où il n'y en a pas et ferait écrire une strophe à la main sans nécessité. D'où la suggestion portée en commentaire sur l'issue : formuler le garde-fou sur la **version résolue de `windows-sys`** plutôt que sur une liste de crates déclencheurs — la liste est ce qui vient d'être trouvé faux, la version est ce qui est protégé. #310 avait **bien** vu la dérive et l'avait jugée inoffensive *sur Linux*, où elle l'est ; son coût tombe sur le build Windows que personne ne mesurait : **3 versions de `windows-sys` compilées contre 5**. Aucun risque de correction — les 7 crates déclarent des bornes larges couvrant les deux états. Et cargo annonce « Locking 1 package » en écrivant 8 changements, donc le silence est le sien. Décision : garde-fou CI sur la version + ADR portant la condition de sortie. Le check croisé Windows depuis Linux a été **testé et écarté** — `cc-rs` s'arrête sur `lib.exe`, il faudrait `cargo-xwin`).
- Nouveau bug ouvert : **#320** — les installations `.rpm` ne reçoivent aucune mise à jour (`latest.json` ne porte qu'une entrée `linux-x86_64` construite depuis le `.deb`, `release.yml:104-122`), alors que les `.rpm` continuent d'être publiés en assets. Documenté en §3 de `docs/qa-update-cycle.md` (« ne pas tester : cassé, pas seulement non vérifié »). - Nouveau bug ouvert : **#320** — les installations `.rpm` ne reçoivent aucune mise à jour (`latest.json` ne porte qu'une entrée `linux-x86_64` construite depuis le `.deb`, `release.yml:104-122`), alors que les `.rpm` continuent d'être publiés en assets. Documenté en §3 de `docs/qa-update-cycle.md` (« ne pas tester : cassé, pas seulement non vérifié »).
- Suivis ouverts du chantier import (milestone fermée) : le refus du format « montant absolu + indicateur D/C » ne scanne que `amountCol ± 1`, donc une colonne D/C placée ailleurs passe encore ; dérives de comptes préexistantes dans `CLAUDE.md` et `docs/architecture.md` (composants, pages, services, `Version actuelle : 0.6.3` vs tag v0.14.0) mesurées et documentées en revue, non corrigées. - Suivis ouverts du chantier import (milestone fermée) : le refus du format « montant absolu + indicateur D/C » ne scanne que `amountCol ± 1`, donc une colonne D/C placée ailleurs passe encore ; dérives de comptes préexistantes dans `CLAUDE.md` et `docs/architecture.md` (composants, pages, services, `Version actuelle : 0.6.3` vs tag v0.14.0) mesurées et documentées en revue, non corrigées.
- **Étape 10 de la release v0.15.0 consignée le 2026-09-13** — plus un blocker : cycle réel v0.14.0 → v0.15.0 validé par les deux testeurs ; trace rédigée pour le corps de la release (`PATCH` à appliquer par Max) ; invite d'authentification Linux non consignée ; #320 reste ouverte (`.rpm`). Deux restes : (1) le texte de la carte Compte Maximus (« Le compte est requis uniquement pour les fonctionnalités Premium », hérité de v0.7) contredit v0.15.0 où Premium vient de la clé, et a vraisemblablement conduit un testeur à se connecter pour rien (code de vérification Logto reçu par courriel) — reformulation à ouvrir en issue ; (2) le flux Logto exact (connexion par code, mot de passe oublié ou inscription) reste à lire dans le journal d'audit. Position produit : v0.15.0, `[Unreleased]` vide. - **Étape 10 de la release v0.15.0 consignée le 2026-09-13** — plus un blocker : cycle réel v0.14.0 → v0.15.0 validé par les deux testeurs ; trace rédigée pour le corps de la release (`PATCH` à appliquer par Max) ; invite d'authentification Linux non consignée ; #320 reste ouverte (`.rpm`). Deux restes : (1) le texte de la carte Compte Maximus (« Le compte est requis uniquement pour les fonctionnalités Premium », hérité de v0.7) contredit v0.15.0 où Premium vient de la clé, et a vraisemblablement conduit un testeur à se connecter pour rien (code de vérification Logto reçu par courriel) — reformulation à ouvrir en issue ; (2) le flux Logto exact (connexion par code, mot de passe oublié ou inscription) reste à lire dans le journal d'audit. Position produit : v0.15.0, `[Unreleased]` vide.