feat(gating): socle — LicenseProvider + matrice d'entitlements + useEntitlement #297

Closed
opened 2026-07-19 21:18:20 +00:00 by maximus · 1 comment
Owner

LicenseProvider (Context partage) + matrice d'entitlements TS + hook useEntitlement synchrone. C'est le socle : tout le gating s'y branche.

Fichiers concernes

  • src/contexts/LicenseContext.tsx (creer) — provider modele sur ProfileContext (createContext<T|null>, useReducer, hook qui throw). Charge edition + info 1x, expose { status, edition, features, info, refresh, submitKey }.
  • src/main.tsx (modifier) — monter LicenseProvider AU-DESSUS de ProfileProvider (licence = machine-level, survit au remount BrowserRouter key=refreshKey).
  • src/shared/entitlements.ts (creer) — FeatureKey (kebab-case), ENTITLEMENTS, isEntitled() pur fail-closed Free.
  • src/hooks/useEntitlement.ts (creer) — useEntitlement(f): { allowed, ready }.
  • src/hooks/useIsPremium.ts + useIsPremium.test.ts (modifier) — lire le context ; migrer le mock du test.
  • src/components/settings/LicenseCard.tsx (modifier) — consommer le context.
  • src/services/entitlements.test.ts (creer).

Depends on

  • aucune (fondation)

Criteres d'acceptation

  • useEntitlement retourne { allowed, ready } (ready = status==="ready"), PAS un boolean.
  • isEntitled est fail-closed en Free : if (edition === "free") return false AVANT l'override features[].
  • LicenseProvider monte au-dessus de ProfileProvider ; un changement de profil ne recharge pas la licence.
  • Sur erreur de boot (invoke throw), le provider expose un etat neutre + refresh, JAMAIS l'upsell (un payant n'est pas bloque par un echec transitoire).
  • useIsPremium behavior-preserving : Premium => true ; le gate cours (PriceFetchControl) ne regresse pas.
  • entitlements.test.ts : matrice, override features[], override IGNORE quand edition==="free", feature absente. Suite verte.

Review caveats (/review-spec — a respecter)

  • CWE-863 : l'override features[] doit etre fail-closed en Free (une cle copiee downgrade free mais expose ses features signees).
  • CWE-703 : LicenseProvider est un SPOF — recuperation d'erreur obligatoire (retry/backoff + etat neutre).
  • useIsPremium.test.ts mocke ./useLicense (vi.mock) — le refactor le casse ; migrer vers le mock du context.
  • Cles en kebab-case (namespace features[] partage Rust/TS).

Decisions prises en planification

  • Modele : matrice statique tier->features + override par-licence features[] (fail-closed Free).
  • Enforcement UI-only (soft-paywall GPL assume).

Spec source

spec-decisions-feature-gating.md + spec-plan-feature-gating.md (racine, force-add ; verdict /review-spec jaune, corrections integrees).

LicenseProvider (Context partage) + matrice d'entitlements TS + hook useEntitlement synchrone. C'est le socle : tout le gating s'y branche. ## Fichiers concernes - `src/contexts/LicenseContext.tsx` (creer) — provider modele sur ProfileContext (createContext<T|null>, useReducer, hook qui throw). Charge edition + info 1x, expose { status, edition, features, info, refresh, submitKey }. - `src/main.tsx` (modifier) — monter LicenseProvider AU-DESSUS de ProfileProvider (licence = machine-level, survit au remount BrowserRouter key=refreshKey). - `src/shared/entitlements.ts` (creer) — FeatureKey (kebab-case), ENTITLEMENTS, isEntitled() pur fail-closed Free. - `src/hooks/useEntitlement.ts` (creer) — useEntitlement(f): { allowed, ready }. - `src/hooks/useIsPremium.ts` + `useIsPremium.test.ts` (modifier) — lire le context ; migrer le mock du test. - `src/components/settings/LicenseCard.tsx` (modifier) — consommer le context. - `src/services/entitlements.test.ts` (creer). ## Depends on - aucune (fondation) ## Criteres d'acceptation - [ ] useEntitlement retourne `{ allowed, ready }` (ready = status==="ready"), PAS un boolean. - [ ] isEntitled est fail-closed en Free : `if (edition === "free") return false` AVANT l'override features[]. - [ ] LicenseProvider monte au-dessus de ProfileProvider ; un changement de profil ne recharge pas la licence. - [ ] Sur erreur de boot (invoke throw), le provider expose un etat neutre + refresh, JAMAIS l'upsell (un payant n'est pas bloque par un echec transitoire). - [ ] useIsPremium behavior-preserving : Premium => true ; le gate cours (PriceFetchControl) ne regresse pas. - [ ] entitlements.test.ts : matrice, override features[], override IGNORE quand edition==="free", feature absente. Suite verte. ## Review caveats (/review-spec — a respecter) - CWE-863 : l'override features[] doit etre fail-closed en Free (une cle copiee downgrade free mais expose ses features signees). - CWE-703 : LicenseProvider est un SPOF — recuperation d'erreur obligatoire (retry/backoff + etat neutre). - useIsPremium.test.ts mocke `./useLicense` (vi.mock) — le refactor le casse ; migrer vers le mock du context. - Cles en kebab-case (namespace features[] partage Rust/TS). ## Decisions prises en planification - Modele : matrice statique tier->features + override par-licence features[] (fail-closed Free). - Enforcement UI-only (soft-paywall GPL assume). ## Spec source spec-decisions-feature-gating.md + spec-plan-feature-gating.md (racine, force-add ; verdict /review-spec jaune, corrections integrees).
maximus added this to the planned-2026-07-19-feature-gating milestone 2026-07-19 21:18:20 +00:00
maximus added the
status:ready
type:feature
source:human
labels 2026-07-19 21:18:20 +00:00
Author
Owner

Corrections /review-spec a integrer :

  • useEntitlement retourne { allowed, ready } (pas un boolean) — sinon flash upsell au boot (Sidebar/tuiles/profils/routes).
  • isEntitled fail-closed en Free : if (edition === 'free') return false avant l override features[] — une cle copiee downgrade en free (machine-binding) mais expose ses features signees (CWE-863).
  • Migrer useIsPremium.test.ts : il mocke ./useLicense, casse par le refactor vers le context.
  • LicenseProvider : recuperation d erreur (retry + etat neutre, jamais upsell) si le boot invoke throw (CWE-703).
  • Cles en kebab-case (reports-advanced). Voir spec-plan § Revision — Synthese.
Corrections /review-spec a integrer : - `useEntitlement` retourne `{ allowed, ready }` (pas un boolean) — sinon flash upsell au boot (Sidebar/tuiles/profils/routes). - `isEntitled` **fail-closed en Free** : `if (edition === 'free') return false` avant l override `features[]` — une cle copiee downgrade en free (machine-binding) mais expose ses features signees (CWE-863). - Migrer `useIsPremium.test.ts` : il mocke `./useLicense`, casse par le refactor vers le context. - LicenseProvider : recuperation d erreur (retry + etat neutre, jamais upsell) si le boot invoke throw (CWE-703). - Cles en kebab-case (`reports-advanced`). Voir spec-plan § Revision — Synthese.
maximus added
status:needs-fix
and removed
status:ready
labels 2026-07-21 01:14:21 +00:00
maximus added
status:approved
and removed
status:needs-fix
labels 2026-07-21 01:52:16 +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#297
No description provided.