feat(gating): auto-update Base+ + features[] override + dev-override (Rust) #307
No reviewers
Labels
No labels
autopilot:pending-human
source:analyste
source:defenseur
source:human
source:medic
status:approved
status:blocked
status:in-progress
status:needs-clarification
status:needs-fix
status:ready
status:review
status:triage
type:bug
type:feature
type:infra
type:refactor
type:schema
type:security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: maximus/Simpl-Resultat#307
Loading…
Reference in a new issue
No description provided.
Delete branch "issue-301-rust-entitlements"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
4e maillon de la pile gating (base =
issue-300-multi-profile-gate, pasmain). 100% Rust — le front ne bouge pas d'un octet.Ce que ça fait (en termes JS/TS)
1. Auto-update passe Base+ (absorbe #271).
FEATURE_TIERS(l'équivalent Rust de notreENTITLEMENTSdesrc/shared/entitlements.ts) passeauto-updatede[free, base, premium]à[base, premium]. Le commentaire « temporarily open until the license server is live » saute — le serveur est live depuis spec-monetisation. Le testfree_allows_auto_update_temporarilydevientfree_denied_auto_update. Côté UI rien à faire :useUpdater.tsappelle déjàcheck_entitlement("auto-update")et gère l'étatnotEntitled.2. Purge des entrées mortes.
web-sync,cloud-backup,advanced-reportsn'avaient aucun call-site, etadvanced-reports → Premiumcontredisait la matrice TS (reports-advanced → Base+). Il ne reste queauto-updatecôté Rust — la matrice UI vit uniquement en TS, plus de double source de vérité.3. Override
features[]fail-closed en Free (CWE-863). Le JWT de licence porte un tableau signéfeatures[](namespace kebab-case partagé avec le TS). Nouveau résolveurcurrent_entitlements(): il retourne(edition, features)en suivant exactement le même chemin machine-binding que l'édition — chaque chemin qui downgrade àfree(clé copiée, activation.token d'une autre machine, JWT invalide/expiré) retourne("free", []). Une licence copiée perd donc ses features signées en même temps que son édition.check_entitlementcombine ensuite via la nouvelle fonction pureis_entitled():is_feature_allowed(feature, edition) || features.includes(feature), avec le même court-circuitedition === "free" → pas d'overrideque notreisEntitledTS (défense en profondeur).current_edition()délègue désormais àcurrent_entitlements().0— une seule implémentation du chemin, aucune dérive possible entre les deux.4. Dev-override pour tester les 3 tiers (CWE-489). Nouvelle Cargo feature
dev-override— l'analogue d'un flag de compilation avec dead-code-elimination garanti : quand la feature est off (le défaut, et release ne l'active jamais), le code qui litSR_DEV_EDITIONn'existe pas dans le binaire (pas juste unifdésactivé, le bloc est retiré à la compilation via#[cfg(feature = "dev-override")]). C'est pour ça qu'on n'utilise pasdebug_assertions: ce dernier peut être réactivé sur un build release par config → backdoor Premium. Usage dev :cargo test --features dev-override, outauri dev -- --features dev-overridepuisSR_DEV_EDITION=premium. Valeur inconnue (typo) = ignorée, résolution normale.Garanties de non-régression
check_entitlement(feature: string): Promise<boolean>reste identique pouruseUpdater.ts/ErrorPage.tsx/licenseService.ts;get_editioninchangé.features: [](il ne lit pas le JWT de licence, donc rien de signé à porter — et Premium a déjà tout).Tests
cargo test(build normal, feature off) : 106 verts (98 baseline ��� 2 tests devenus sans objet après la purge + 10 nouveaux), dontsr_dev_edition_has_no_effect_when_feature_offqui prouve que la var d'env est inerte dans un build normal (sous feature off, aucun code ne lit la var → pas de course possible entre tests parallèles).cargo test --features dev-override: 2 tests compagnons (force premium / valeur inconnue ignorée), accès env sérialisé par mutex (cargo test = threads parallèles, env = global process).features:["balance"]+ activation.tokenmachine-Avérifié depuismachine-B→("free", [])(les features signées ne survivent pas au downgrade).npm test: 871 vitest verts,npm run build(tsc + vite) vert — le front est intact.#271 est déjà fermé superseded, rien à faire dessus.
Resolves #301
Generated autonomously by /autopilot run of 2026-07-20
Review adversariale — APPROVE
Résumé : Implémentation fidèle et complète de l'Issue 5 du plan gating (5/5 cases) : auto-update Base+ (#271 absorbé), purge des 3 entrées mortes, override
features[]fail-closed en Free, dev-override sous Cargo feature. Les deux exigences sécurité du/review-specsont correctement posées. Aucune signature de commande Tauri ne change, aucune migration touchée, 100% Rust.Points vérifiés au-delà du diff
LicenseClaims.featuresporte#[serde(default)](license_commands.rs:58, préexistant) — une licence Base déjà émise sans champfeaturesdésérialise en[]au lieu d'échouer la validation. Pas de downgrade des licences en circulation.resolve_license_entitlements/current_entitlementsretourne("free", []), etis_entitledcourt-circuite l'override quandedition == free(défense en profondeur, alignée sur leisEntitledTS). Testé des deux côtés (override_ignored_in_free,machine_mismatch_downgrades_to_free_and_drops_features).dev-overridehorsdefaultet vérifié absent des builds release —release.ymllancenpx tauri buildsans--features. Sous feature off, le code lisantSR_DEV_EDITIONn'existe pas dans le binaire ; le test d'inertie tourne dans la CI normale.web-sync/cloud-backup/advanced-reportssur la branche (seuls hits : specs, STATE.md, etentitlements.test.tsoù"web-sync"sert de feature inconnue castéeas FeatureKey).current_edition: mêmes checks, mêmes priorités (Compte Premium > licence fichier > free), même posture stricte (token/machine-id illisible → free). Seule différence : l'ordre I/O (lecture du token d'activation avant la validation du JWT), sans effet observable.current_editiondélègue àcurrent_entitlements().0— une seule implémentation, zéro dérive possible.std::env::set_varest une API safe, les tests env compilent ; les compagnons feature-on sérialisent l'accès env par mutex.auto-updateuniquement côté Rust, matrice UI uniquement TS), kebab-case partagé,validate_with_keyrejette toute édition hors {base, premium} doncis_entitledne voit jamais d'édition exotique.Suggestions (non bloquantes)
dev_override_onne tournent jamais en CI (check.ymlne passe pas la feature) — assumé dans le body ; si le dev-override devient un outil quotidien, un job ponctuelcargo test --features dev-overridefermerait le trou.issue-300-multi-profile-gate→ aucune CI n'a tourné dessus. Valider le tip cumulé en local avant le merge de la pile (tsc + vite build, vitest, cargo check, cargo test).Review /pr-review — vérifications read-only sur
origin/issue-301-rust-entitlements(git show/grep, sans checkout).Mergé en fast-forward sur main (pile #304→#308), voir commit
17833cf. Verdict /pr-review : APPROVE. Fermeture manuelle : le merge local ff n est pas détecté comme merged par Forgejo.Pull request closed