feat(licence): retirer la clé enregistrée — arrêter l'envoi quotidien d'une clé révoquée #370

Closed
opened 2026-10-03 13:51:40 +00:00 by maximus · 1 comment
Owner

Contexte

Depuis #365 (PR #368, mergée le 2026-10-03), l'app vérifie au démarrage, au plus une fois par 24 h, que la clé enregistrée n'a pas été révoquée. À la revue, Max a décidé qu'une clé connue comme révoquée reste contrôlée tant qu'elle est enregistrée, pour reprendre une correction faite côté serveur (décision 3 du 2026-10-03, consignée dans l'amendement #365 de l'ADR 0017).

Or aucune interface ne retire une clé enregistrée. Aucune commande Rust ne supprime license.key ; seuls store_license_in et la désactivation suppriment activation.token. Un acheteur remboursé qui continue en Gratuite envoie donc sa clé révoquée au serveur une fois par 24 h, sauf s'il entre une autre clé ou supprime le fichier à la main. Le guide le dit tel quel depuis #365.

Historique : la traduction license.removeLicense existait sans bouton ni commande ; la décision D5 du /plan-run du 2026-09-24 l'a retirée et #347 l'a supprimée. Cette décision précède #365 : la raison d'offrir le retrait est nouvelle.

Analyse (/analyze, 2026-10-03, main 0e4fef5)

Prémisse confirmée dans le code. Points établis :

  • entitlements_from_dir n'a besoin d'aucun changement : sans license.key, il rend déjà ("free", []) (license_commands.rs:368-376), et check_revocation_in rend no_license sans rien envoyer (L577-579).
  • Rien hors de license_commands.rs ne lit license.key. activation.token est aussi lu par la route des cours (balance_commands.rs:102-111) : le retirer coupe les cours, ce qui est voulu.
  • Pour une clé révoquée, l'édition est Gratuite, donc la section Machines est masquée (LicenseCard.tsx:30, :171) et le serveur a déjà libéré les places (revokeLicense). La question de la place ne se pose que pour une clé valide et activée.
  • Après un refresh(), LOAD_DONE arrive avec info: null et revoked vaut false (LicenseContext.tsx:244) ; la carte se remonte en no-license (licenseIdentity, LicenseCard.tsx:18-21) et useLicenseMachines repart de zéro. Un Premium de compte Maximus reste résolu par getEdition.
  • Il n'existe pas de composant de confirmation générique : le motif dominant est window.confirm(t(...)) (TemplateActions.tsx:61, AccountsPage.tsx:126, ProfileFormModal.tsx:91, etc.).

Décisions

  1. license.check est conservé (décision de l'analyse). Il est indexé par le condensé de la clé, donc il ne gêne aucune autre clé. Le supprimer rendrait son édition à une clé révoquée retirée puis ressaisie, jusqu'au contrôle suivant : un contournement mis à la portée de tous par un bouton. Avec #373, une clé ressaisie est de toute façon recontrôlée si le contrôle est dû.
  2. Désactiver d'abord (décision de Max, 2026-10-03). Quand la clé est valide et que cette machine est activée, le retrait propose de libérer la place sur le serveur avant de retirer la clé. Si la désactivation échoue (hors ligne, serveur en erreur), une seconde confirmation demande s'il faut retirer quand même, avec l'avertissement que la place reste occupée et se libère depuis une autre machine qui a la clé. Écarté : le retrait local seul, qui brûle une place à l'utilisateur qui retire sa clé avant de changer de poste.
  3. Confirmation selon l'état, en FR et en EN. Clé valide : les modules se verrouillent, les données restent. Clé révoquée : l'envoi quotidien s'arrête.

Point de conception. Depuis #350, ActivationStatus ne porte plus d'identifiant de machine. Le front connaît is_activated sans requête (useLicenseMachines, loadActivationStatus) et choisit avec lui le texte de confirmation et release_slot ; l'identifiant à désactiver reste côté Rust. Par exemple, remove_license(release_slot: bool) : avec release_slot, lire l'identifiant du jeton valide pour ce poste (stored_token_machine_id), le désactiver (deactivate_in), puis supprimer les fichiers. Un échec réseau rend une erreur codée sans rien supprimer, et le front repose la question puis rappelle avec release_slot: false. Une forme en deux commandes est acceptable si elle garde ces règles.

Travail à faire

  • Rust : remove_license_in(dir, ...) sans AppHandle (modèle store_license / store_license_impl / store_license_in), commande remove_license enregistrée dans lib.rs. Supprime license.key d'abord (c'est ce qui arrête l'envoi), puis activation.token via le helper partagé remove_activation_token_in (voir « Décisions »), garde license.check. Une suppression qui échoue rend storage ; aucun état où le jeton est parti et la clé reste. Idempotente sans clé ; storage si un fichier ne peut pas être supprimé. Désactivation préalable comme décrit ci-dessus, note_revocation sur la clé envoyée si le serveur répond license_revoked.
  • TS : removeLicense dans licenseService.ts (à côté de storeLicense) ; orchestration dans une fonction exportée à dépendances injectées performRemoveLicense(deps) (confirm, removeLicense, refresh, logInfo, logWarn, état is_activated/revoked), sur le modèle de performSubmitKey ; bouton « Retirer la clé » dans LicenseCard, qui passe window.confirm et t(...), visible quand info !== null (clé enregistrée, valide ou révoquée), pas selon hasLicense (edition !== "free", qui le cacherait pour une clé révoquée) ; une ligne au Journal par retrait (logInfo), et par désactivation préalable échouée (logWarn).
  • i18n FR et EN sous license.* (bouton, deux ou trois textes de confirmation, avertissement de place occupée, erreur nommée).
  • Docs : guide (docs/guide-utilisateur.md:566) et docs.editions.tips (fr.json/en.json:1130), où « Seul l'enregistrement d'une autre clé arrête l'envoi de la précédente » devient le retrait de la clé ; docs/architecture.md (liste des commandes L340-348, et L350/L354) ; ADR 0017, amendement #365 (L19 : « aucune interface ne retire une clé enregistrée ») ; CHANGELOG.md et CHANGELOG.fr.md (Added/Ajouté).

Surface de test

  • Existants : Rust store_* (L1629-1700), missing_key_file_resolves_free (L1608), a_revoked_key_resolves_free_without_features (L2159), nothing_is_sent_without_a_valid_stored_key_nor_before_24_hours (L2325), deactivation_drops_the_token_that_stood_for_the_freed_slot (L2002), helper verify_route (L2112) ; TS LicenseContext.test.ts, LicenseCard.render.test.tsx, licenseService.test.ts.
  • À ajouter, Rust :
    • Retrait : license.key et activation.token supprimés, license.check intact, rien d'autre ne bouge.
    • Après le retrait, entitlements_from_dir rend ("free", []) et check_revocation_in rend no_license sans requête (un seul mock attrape-tout expect(0), sans mock valide à côté, cf. la sélection des mocks de mockito).
    • Retrait sans clé : succès, rien n'est écrit.
    • release_slot : la désactivation part avec l'identifiant du jeton (vérifié par match_request), puis les fichiers partent ; un échec réseau ne supprime rien et rend une erreur codée.
  • À ajouter, front :
    • Le bouton apparaît avec une clé valide et avec une clé révoquée en édition free (rendu statique) ; il n'apparaît pas sans clé ; le texte de confirmation dépend de l'état (valide, révoquée, activée).
    • performRemoveLicense : chaque ligne de la table d'erreurs ; la seconde confirmation n'apparaît que pour network, server, rate_limit_exceeded, invalid_response.
    • Rust : échec de suppression de license.key → storage, jeton intact.
    • La confirmation refusée n'appelle rien ; acceptée, elle appelle le retrait puis recharge le contexte ; un échec est nommé.
  • Test de régression : non requis (type:feature), mutations attendues sur chaque règle ci-dessus.

Critères d'acceptation

  • Un utilisateur peut retirer sa clé depuis la carte Licence, après confirmation.
  • Une machine activée avec une clé valide libère sa place sur le serveur avant le retrait, ou l'utilisateur est averti que la place reste occupée.
  • Après le retrait, aucune requête de contrôle ne part au démarrage.
  • Une clé révoquée retirée puis ressaisie reste en Gratuite : par license.check si aucune autre clé n'a été contrôlée entre-temps, sinon par le contrôle à la saisie de #373 (en ligne). Hors ligne après le contrôle d'une autre clé, elle garde son édition jusqu'au contrôle suivant (fail-open assumé, ADR 0017).
  • Les données des modules verrouillés sont conservées.
  • Le guide dit comment arrêter le contrôle.
  • cargo test, npm test et la CI verts.

Vérification manuelle de #346 : faite le 2026-10-04

Faite par Max sur la v0.16.0 (Linux) sans attendre cette issue : l'édition Gratuite venait de la clé révoquée de l'achat de contrôle (#365), et non d'un retrait de clé. Sélecteur de profils, création d'un profil, écran verrouillé dans le modal (avec la ligne qui nomme la révocation), puis « J'ai déjà une clé » : le modal se ferme et la carte Licence s'affiche, sans modal resté par-dessus. La seule mutation de #346 que ses tests ne gardent pas (ProfileFormModal sans onNavigate={onClose}, PR #362) est donc couverte, et cette issue n'a plus à l'attendre.

Complexité estimée

Moyenne (Rust + front + docs, la désactivation préalable ajoute un chemin réseau).

Ordre

Après #373 : même fichier LicenseContext.tsx, même amendement de l'ADR 0017. Avant ou après #367 indifféremment, mais pas en parallèle : les trois touchent license_commands.rs et la carte Licence.

Liens

Fichiers concernés

  • src-tauri/src/commands/license_commands.rs (remove_license_in, commande, helper remove_activation_token_in partagé avec #367), src-tauri/src/lib.rs (enregistrement)
  • src/services/licenseService.ts (+ licenseService.test.ts)
  • src/contexts/LicenseContext.tsx (+ LicenseContext.test.ts) ou src/hooks/useLicenseMachines.ts pour performRemoveLicense
  • src/components/settings/LicenseCard.tsx (+ LicenseCard.render.test.tsx)
  • src/i18n/locales/fr.json, en.json ; docs/guide-utilisateur.md, docs/architecture.md, docs/adr/0017-feature-gating-par-tier.md ; CHANGELOG.md, CHANGELOG.fr.md

Depends on

Décisions prises en planification

  • Séance /plan-run du 2026-10-03, revue /review-spec (3 experts).
  • Table des erreurs de la désactivation préalable (release_slot) :
    • license_revoked (avec note_revocation sous la clé envoyée), license_not_found, no_active_activation → la place est déjà libre, le retrait continue ;
    • network, server, rate_limit_exceeded, invalid_response → erreur codée, rien n'est supprimé, le front repose la question (« retirer quand même ? ») puis rappelle avec release_slot: false ;
    • jeton absent ou invalide pour ce poste (stored_token_machine_id rend None), ou clé connue comme révoquée → retrait sans appel réseau, release_slot ignoré.
  • Ordre de suppression : license.key, puis activation.token.
  • Helper pub(crate) fn remove_activation_token_in(dir) -> Result<(), LicenseError> qui tolère NotFound, utilisé ici et par #367 ; store_license_in et drop_token_of_deactivated_machine_in peuvent l'adopter sans changer leur comportement d'erreur.
  • PR en Fixes #370 : la vérification manuelle de #346, qui justifiait Refs et un passage en status:blocked après la PR, est faite depuis le 2026-10-04.

Review caveats

  • 🔴 TECHNIQUE (résolu) : critères manuels et écritures externes sortis vers « Après merge (Max) ».
  • 🟡 TECHNIQUE : hasLicense exclut la clé révoquée → bouton sur info !== null.
  • 🟡 SECURITE, TECHNIQUE : erreurs de release_slot → table ci-dessus.
  • 🟡 ARCHITECTURE : orchestration des confirmations en fonction pure testable.
  • 🟡 TECHNIQUE : license.check ne garde qu'une clé → critère borné au cas en ligne.
  • 🟢 SECURITE : ordre de suppression et état partiel.
  • Les numéros de ligne cités datent de main 0e4fef5 et dérivent dans la pile : se fier aux noms de fonctions, intitulés de paragraphes et clés i18n (décision de planification).

Après merge (Max)

  • Le worker ne commente pas maximus/la-compagnie-maximus#184 : il rédige dans le DAILY-REPORT le texte à y poster (« le retrait de la clé, dans Paramètres → Utilisateurs → Licence, arrête le contrôle »). Max le poste.

Spec source

spec-plan-suites-licence.md (local, ignoré par git)

## Contexte Depuis #365 (PR #368, mergée le 2026-10-03), l'app vérifie au démarrage, au plus une fois par 24 h, que la clé enregistrée n'a pas été révoquée. À la revue, Max a décidé qu'une clé connue comme révoquée **reste contrôlée** tant qu'elle est enregistrée, pour reprendre une correction faite côté serveur (décision 3 du 2026-10-03, consignée dans l'amendement #365 de l'ADR 0017). Or aucune interface ne retire une clé enregistrée. Aucune commande Rust ne supprime `license.key` ; seuls `store_license_in` et la désactivation suppriment `activation.token`. Un acheteur remboursé qui continue en Gratuite envoie donc sa clé révoquée au serveur une fois par 24 h, sauf s'il entre une autre clé ou supprime le fichier à la main. Le guide le dit tel quel depuis #365. Historique : la traduction `license.removeLicense` existait sans bouton ni commande ; la décision D5 du `/plan-run` du 2026-09-24 l'a retirée et #347 l'a supprimée. Cette décision précède #365 : la raison d'offrir le retrait est nouvelle. ## Analyse (`/analyze`, 2026-10-03, `main` `0e4fef5`) Prémisse confirmée dans le code. Points établis : - `entitlements_from_dir` n'a besoin d'aucun changement : sans `license.key`, il rend déjà `("free", [])` (`license_commands.rs:368-376`), et `check_revocation_in` rend `no_license` sans rien envoyer (L577-579). - Rien hors de `license_commands.rs` ne lit `license.key`. `activation.token` est aussi lu par la route des cours (`balance_commands.rs:102-111`) : le retirer coupe les cours, ce qui est voulu. - Pour une clé révoquée, l'édition est Gratuite, donc la section Machines est masquée (`LicenseCard.tsx:30`, `:171`) et le serveur a déjà libéré les places (`revokeLicense`). La question de la place ne se pose que pour une clé **valide et activée**. - Après un `refresh()`, `LOAD_DONE` arrive avec `info: null` et `revoked` vaut `false` (`LicenseContext.tsx:244`) ; la carte se remonte en `no-license` (`licenseIdentity`, `LicenseCard.tsx:18-21`) et `useLicenseMachines` repart de zéro. Un Premium de compte Maximus reste résolu par `getEdition`. - Il n'existe pas de composant de confirmation générique : le motif dominant est `window.confirm(t(...))` (`TemplateActions.tsx:61`, `AccountsPage.tsx:126`, `ProfileFormModal.tsx:91`, etc.). ## Décisions 1. **`license.check` est conservé** (décision de l'analyse). Il est indexé par le condensé de la clé, donc il ne gêne aucune autre clé. Le supprimer rendrait son édition à une clé révoquée retirée puis ressaisie, jusqu'au contrôle suivant : un contournement mis à la portée de tous par un bouton. Avec #373, une clé ressaisie est de toute façon recontrôlée si le contrôle est dû. 2. **Désactiver d'abord (décision de Max, 2026-10-03).** Quand la clé est valide et que cette machine est activée, le retrait propose de libérer la place sur le serveur avant de retirer la clé. Si la désactivation échoue (hors ligne, serveur en erreur), une seconde confirmation demande s'il faut retirer quand même, avec l'avertissement que la place reste occupée et se libère depuis une autre machine qui a la clé. Écarté : le retrait local seul, qui brûle une place à l'utilisateur qui retire sa clé avant de changer de poste. 3. **Confirmation selon l'état**, en FR et en EN. Clé valide : les modules se verrouillent, les données restent. Clé révoquée : l'envoi quotidien s'arrête. **Point de conception.** Depuis #350, `ActivationStatus` ne porte plus d'identifiant de machine. Le front connaît `is_activated` sans requête (`useLicenseMachines`, `loadActivationStatus`) et choisit avec lui le texte de confirmation et `release_slot` ; l'identifiant à désactiver reste côté Rust. Par exemple, `remove_license(release_slot: bool)` : avec `release_slot`, lire l'identifiant du jeton valide pour ce poste (`stored_token_machine_id`), le désactiver (`deactivate_in`), puis supprimer les fichiers. Un échec réseau rend une erreur codée sans rien supprimer, et le front repose la question puis rappelle avec `release_slot: false`. Une forme en deux commandes est acceptable si elle garde ces règles. ## Travail à faire - [ ] Rust : `remove_license_in(dir, ...)` sans `AppHandle` (modèle `store_license` / `store_license_impl` / `store_license_in`), commande `remove_license` enregistrée dans `lib.rs`. Supprime **`license.key` d'abord** (c'est ce qui arrête l'envoi), puis `activation.token` via le helper partagé `remove_activation_token_in` (voir « Décisions »), garde `license.check`. Une suppression qui échoue rend `storage` ; aucun état où le jeton est parti et la clé reste. Idempotente sans clé ; `storage` si un fichier ne peut pas être supprimé. Désactivation préalable comme décrit ci-dessus, `note_revocation` sur la clé envoyée si le serveur répond `license_revoked`. - [ ] TS : `removeLicense` dans `licenseService.ts` (à côté de `storeLicense`) ; orchestration dans une fonction exportée à dépendances injectées `performRemoveLicense(deps)` (`confirm`, `removeLicense`, `refresh`, `logInfo`, `logWarn`, état `is_activated`/`revoked`), sur le modèle de `performSubmitKey` ; bouton « Retirer la clé » dans `LicenseCard`, qui passe `window.confirm` et `t(...)`, **visible quand `info !== null`** (clé enregistrée, valide ou révoquée), pas selon `hasLicense` (`edition !== "free"`, qui le cacherait pour une clé révoquée) ; une ligne au Journal par retrait (`logInfo`), et par désactivation préalable échouée (`logWarn`). - [ ] i18n FR et EN sous `license.*` (bouton, deux ou trois textes de confirmation, avertissement de place occupée, erreur nommée). - [ ] Docs : guide (`docs/guide-utilisateur.md:566`) et `docs.editions.tips` (`fr.json`/`en.json:1130`), où « Seul l'enregistrement d'une autre clé arrête l'envoi de la précédente » devient le retrait de la clé ; `docs/architecture.md` (liste des commandes L340-348, et L350/L354) ; ADR 0017, amendement #365 (L19 : « aucune interface ne retire une clé enregistrée ») ; `CHANGELOG.md` et `CHANGELOG.fr.md` (Added/Ajouté). ## Surface de test - Existants : Rust `store_*` (L1629-1700), `missing_key_file_resolves_free` (L1608), `a_revoked_key_resolves_free_without_features` (L2159), `nothing_is_sent_without_a_valid_stored_key_nor_before_24_hours` (L2325), `deactivation_drops_the_token_that_stood_for_the_freed_slot` (L2002), helper `verify_route` (L2112) ; TS `LicenseContext.test.ts`, `LicenseCard.render.test.tsx`, `licenseService.test.ts`. - À ajouter, Rust : - [ ] Retrait : `license.key` et `activation.token` supprimés, `license.check` intact, rien d'autre ne bouge. - [ ] Après le retrait, `entitlements_from_dir` rend `("free", [])` et `check_revocation_in` rend `no_license` sans requête (un seul mock attrape-tout `expect(0)`, sans mock valide à côté, cf. la sélection des mocks de mockito). - [ ] Retrait sans clé : succès, rien n'est écrit. - [ ] `release_slot` : la désactivation part avec l'identifiant du jeton (vérifié par `match_request`), puis les fichiers partent ; un échec réseau ne supprime rien et rend une erreur codée. - À ajouter, front : - [ ] Le bouton apparaît avec une clé valide **et** avec une clé révoquée en édition free (rendu statique) ; il n'apparaît pas sans clé ; le texte de confirmation dépend de l'état (valide, révoquée, activée). - [ ] `performRemoveLicense` : chaque ligne de la table d'erreurs ; la seconde confirmation n'apparaît que pour `network`, `server`, `rate_limit_exceeded`, `invalid_response`. - [ ] Rust : échec de suppression de `license.key` → `storage`, jeton intact. - [ ] La confirmation refusée n'appelle rien ; acceptée, elle appelle le retrait puis recharge le contexte ; un échec est nommé. - Test de régression : non requis (`type:feature`), mutations attendues sur chaque règle ci-dessus. ## Critères d'acceptation - [ ] Un utilisateur peut retirer sa clé depuis la carte Licence, après confirmation. - [ ] Une machine activée avec une clé valide libère sa place sur le serveur avant le retrait, ou l'utilisateur est averti que la place reste occupée. - [ ] Après le retrait, aucune requête de contrôle ne part au démarrage. - [ ] Une clé révoquée retirée puis ressaisie reste en Gratuite : par `license.check` si aucune autre clé n'a été contrôlée entre-temps, sinon par le contrôle à la saisie de #373 (en ligne). Hors ligne après le contrôle d'une autre clé, elle garde son édition jusqu'au contrôle suivant (fail-open assumé, ADR 0017). - [ ] Les données des modules verrouillés sont conservées. - [ ] Le guide dit comment arrêter le contrôle. - [ ] `cargo test`, `npm test` et la CI verts. ## Vérification manuelle de #346 : faite le 2026-10-04 Faite par Max sur la v0.16.0 (Linux) sans attendre cette issue : l'édition Gratuite venait de la clé révoquée de l'achat de contrôle (#365), et non d'un retrait de clé. Sélecteur de profils, création d'un profil, écran verrouillé dans le modal (avec la ligne qui nomme la révocation), puis « J'ai déjà une clé » : le modal se ferme et la carte Licence s'affiche, sans modal resté par-dessus. La seule mutation de #346 que ses tests ne gardent pas (`ProfileFormModal` sans `onNavigate={onClose}`, PR #362) est donc couverte, et cette issue n'a plus à l'attendre. ## Complexité estimée Moyenne (Rust + front + docs, la désactivation préalable ajoute un chemin réseau). ## Ordre Après #373 : même fichier `LicenseContext.tsx`, même amendement de l'ADR 0017. Avant ou après #367 indifféremment, mais pas en parallèle : les trois touchent `license_commands.rs` et la carte Licence. ## Liens - #365 (contrôle de révocation, décision 3 de la revue) et PR #368 - #347 (retrait de `license.removeLicense`, D5 du 2026-09-24) - #373 (contrôle à la saisie), #367 (jeton d'activation périmé), maximus/la-compagnie-maximus#184 (politique de confidentialité) ## Fichiers concernés - `src-tauri/src/commands/license_commands.rs` (`remove_license_in`, commande, helper `remove_activation_token_in` partagé avec #367), `src-tauri/src/lib.rs` (enregistrement) - `src/services/licenseService.ts` (+ `licenseService.test.ts`) - `src/contexts/LicenseContext.tsx` (+ `LicenseContext.test.ts`) ou `src/hooks/useLicenseMachines.ts` pour `performRemoveLicense` - `src/components/settings/LicenseCard.tsx` (+ `LicenseCard.render.test.tsx`) - `src/i18n/locales/fr.json`, `en.json` ; `docs/guide-utilisateur.md`, `docs/architecture.md`, `docs/adr/0017-feature-gating-par-tier.md` ; `CHANGELOG.md`, `CHANGELOG.fr.md` ## Depends on - #373 ## Décisions prises en planification - Séance `/plan-run` du 2026-10-03, revue `/review-spec` (3 experts). - **Table des erreurs de la désactivation préalable (`release_slot`)** : - `license_revoked` (avec `note_revocation` sous la clé envoyée), `license_not_found`, `no_active_activation` → la place est déjà libre, le retrait continue ; - `network`, `server`, `rate_limit_exceeded`, `invalid_response` → erreur codée, **rien n'est supprimé**, le front repose la question (« retirer quand même ? ») puis rappelle avec `release_slot: false` ; - jeton absent ou invalide pour ce poste (`stored_token_machine_id` rend `None`), ou clé connue comme révoquée → retrait sans appel réseau, `release_slot` ignoré. - Ordre de suppression : `license.key`, puis `activation.token`. - Helper `pub(crate) fn remove_activation_token_in(dir) -> Result<(), LicenseError>` qui tolère `NotFound`, utilisé ici et par #367 ; `store_license_in` et `drop_token_of_deactivated_machine_in` peuvent l'adopter sans changer leur comportement d'erreur. - PR en **`Fixes #370`** : la vérification manuelle de #346, qui justifiait `Refs` et un passage en `status:blocked` après la PR, est faite depuis le 2026-10-04. ## Review caveats - 🔴 TECHNIQUE (résolu) : critères manuels et écritures externes sortis vers « Après merge (Max) ». - 🟡 TECHNIQUE : `hasLicense` exclut la clé révoquée → bouton sur `info !== null`. - 🟡 SECURITE, TECHNIQUE : erreurs de `release_slot` → table ci-dessus. - 🟡 ARCHITECTURE : orchestration des confirmations en fonction pure testable. - 🟡 TECHNIQUE : `license.check` ne garde qu'une clé → critère borné au cas en ligne. - 🟢 SECURITE : ordre de suppression et état partiel. - Les numéros de ligne cités datent de `main` `0e4fef5` et dérivent dans la pile : se fier aux noms de fonctions, intitulés de paragraphes et clés i18n (décision de planification). ## Après merge (Max) - Le worker ne commente pas maximus/la-compagnie-maximus#184 : il rédige dans le DAILY-REPORT le texte à y poster (« le retrait de la clé, dans Paramètres → Utilisateurs → Licence, arrête le contrôle »). Max le poste. ## Spec source spec-plan-suites-licence.md (local, ignoré par git)
maximus added the
status:triage
type:feature
source:human
labels 2026-10-03 13:51:40 +00:00
maximus added
status:ready
and removed
status:triage
labels 2026-10-03 17:53:09 +00:00
maximus added this to the planned-2026-10-03-suites-licence milestone 2026-10-03 19:08:57 +00:00
Author
Owner

Vérification manuelle de #346 faite le 2026-10-04 par Max, sur la v0.16.0 (Linux), sans attendre cette issue. L'édition Gratuite venait de la clé révoquée de l'achat de contrôle (#365), encore enregistrée sur le poste. Depuis le sélecteur de profils, la création d'un profil affiche l'écran verrouillé dans le modal, avec la ligne qui nomme la révocation ; « J'ai déjà une clé » ferme le modal et affiche la carte Licence, sans rien par-dessus. La mutation que les tests de #346 ne gardent pas (ProfileFormModal sans onNavigate={onClose}, PR #362) est couverte.

Corps mis à jour en conséquence : la section devient « Vérification manuelle de #346 : faite le 2026-10-04 », et la PR de cette issue portera Fixes #370 au lieu de Refs ; plus de passage en status:blocked après la PR. Reste après merge le texte pour maximus/la-compagnie-maximus#184, rédigé dans le DAILY-REPORT et posté par Max.

**Vérification manuelle de #346 faite le 2026-10-04 par Max, sur la v0.16.0 (Linux), sans attendre cette issue.** L'édition Gratuite venait de la clé révoquée de l'achat de contrôle (#365), encore enregistrée sur le poste. Depuis le sélecteur de profils, la création d'un profil affiche l'écran verrouillé dans le modal, avec la ligne qui nomme la révocation ; « J'ai déjà une clé » ferme le modal et affiche la carte Licence, sans rien par-dessus. La mutation que les tests de #346 ne gardent pas (`ProfileFormModal` sans `onNavigate={onClose}`, PR #362) est couverte. Corps mis à jour en conséquence : la section devient « Vérification manuelle de #346 : faite le 2026-10-04 », et la PR de cette issue portera **`Fixes #370`** au lieu de `Refs` ; plus de passage en `status:blocked` après la PR. Reste après merge le texte pour maximus/la-compagnie-maximus#184, rédigé dans le DAILY-REPORT et posté par Max.
maximus added
status:in-progress
and removed
status:ready
labels 2026-10-04 20:39:52 +00:00
maximus added
status:review
and removed
status:in-progress
labels 2026-10-04 21:16:06 +00:00
maximus added
status:approved
and removed
status:review
labels 2026-10-05 01:38:48 +00:00
maximus removed the
status:approved
label 2026-10-05 23:23:26 +00:00
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/Simpl-Resultat#370
No description provided.