Skip to content

Cas d'usage

Vendre des mises à niveau et contrôler les versions

Décidez quelles versions de votre produit chaque licence couvre — mises à jour à vie, période de support, une version majeure, protection de mise à niveau, abonnements avec ou sans repli perpétuel — et appliquez-le dans votre application ou sur votre propre page de téléchargement, sans réactivation, sans nouvelle saisie de clé et avec une prise en charge complète du mode hors ligne.

Objectif

Vendez les mises à jour comme votre activité fonctionne réellement — « toutes les versions 2.x », « toute version publiée dans l’année qui suit l’achat », « tout ce qui sort tant que l’abonnement est actif » — et laissez votre application (et votre propre page de téléchargement) obtenir une réponse oui/non pour n’importe quelle version. C’est la couverture des versions : Keyright enregistre ce que chaque licence couvre, le signe, le livre sur la machine du client avec la lease, et décide de l’éligibilité, sans jamais demander à un client de ressaisir une clé ni de réactiver, et sans interrompre une installation en cours d’exécution.

Keyright est un oracle de couverture : il décide si une licence couvre une version. Il n’héberge jamais de téléchargements, de flux de mise à jour ni de listes de versions — vous continuez d’exploiter votre propre système de mise à jour et votre propre page de téléchargement, et vous demandez à Keyright si un client donné peut disposer d’une version donnée.

Quand l’utiliser

  • Utilisez ceci si vous facturez les mises à niveau, proposez une fenêtre de maintenance, vendez par version majeure, ou exploitez un abonnement pour application de bureau — tout cas où « posséder le logiciel » et « avoir droit à ce build » sont deux questions différentes.
  • Utilisez ceci si vous voulez contrôler l’accès à votre propre page de téléchargement ou à votre système de mise à jour client par client, en ligne ou isolé (air-gapped), depuis une source unique de vérité signée.
  • N’utilisez pas ceci simplement pour empêcher une licence expirée de s’exécuter — c’est le rôle de l’expiration propre à la licence (voir Activer, désactiver et déplacer des sièges). La couverture ajoute la dimension date de publication / version par-dessus.

La couverture des versions fait partie du plan supérieur du service hébergé (Scale et Enterprise) et des licences auto-hébergées qui incluent la fonctionnalité de couverture. Les plans inférieurs voient la fonctionnalité verrouillée dans le tableau de bord avec une invitation à monter de plan. Le SDK est identique quel que soit le plan : une licence sans couverture se lit simplement comme non configurée et exécute toutes les versions.

Fonctionnement

Chaque licence porte un ensemble de conditions de couverture. Une condition est une plage de versions (2.0.0 ≤ v < 3.0.0), une fenêtre de dates (tout ce qui est publié entre deux dates calendaires), ou les deux — éventuellement limitée à un canal de publication (stable par défaut ; les bêtas exigent une condition beta explicite). Une version est couverte lorsqu’au moins une condition correspond à son numéro de version et à sa date d’éligibilité (normalement la date à laquelle elle a été publiée).

Trois propriétés rendent cette application des règles sûre :

  • Un instantané, pas une valeur vive. Lorsque vous émettez une licence, la politique de couverture de l’édition se développe en conditions stockées sur cette licence. Modifier la politique par la suite ne change jamais ce que les clients existants ont acheté ; vous appliquez de nouvelles conditions aux licences existantes par une action explicite et prévisualisée (voir Appliquer une politique aux clients existants).
  • Les conditions ne font que croître. Les conditions forment un registre en ajout seul. Les renouvellements et les mises à niveau ajoutent des conditions ; le support peut retirer une condition, mais rien de ce qu’un client peut exécuter aujourd’hui ne lui est jamais retiré silencieusement par une modification de politique.
  • Signé et hors ligne. La couverture voyage sous la forme d’un bloc de couverture signé séparément, lié à la licence et au produit. L’application le vérifie avec la clé publique qu’elle embarque déjà, le met en cache et décide de la couverture localement — en ligne, hors ligne ou isolée (air-gapped). Un numéro de séquence monotone empêche de rejouer un bloc plus ancien pour restaurer une couverture échue, et un bloc manquant ou altéré n’est jamais considéré comme « couvert ».
Tableau de bordpolitique de couverture d'éditionou conditions par licenceà l'émissionConditions de la licenceinstantané · ajout seulnuméro de séquence croissantsignéBloc de couverturemode · seq · conditionslié à la licence + au produitactiver · valider · rafraîchiren ligne, avec la leaseexport → importfichier air-gappedVotre buildversion · date d'éligibilité · canalembarqués par les props MSBuild KeyrightcomparerDécision (locale, hors ligne)Couverte · Non couverte (fenêtre échue / hors plage)Non configurée · Indisponible hors ligneUn message, une action : Renew · Upgrade · Import filePas de nouvelle saisie de clé. Pas de réactivation.Aucun contrôle de téléchargement dans Keyright.
Politique → conditions sur la licence → bloc signé → l'application compare son propre build au bloc et affiche un message unique avec une action unique.

Deux autres notions que vous rencontrerez tout au long de cette page :

  • Date d’éligibilité et date de publication. La couverture est jugée d’après la date d’éligibilité d’une version, qui vaut par défaut sa date de publication mais peut être définie séparément. C’est ainsi qu’un correctif de sécurité publié après la fin de la fenêtre de support d’un client peut tout de même être couvert pour lui : datez le correctif comme sa version de base.
  • Le registre des versions est facultatif. L’application des règles fonctionne entièrement à partir de la version et de la date d’éligibilité embarquées dans le build lui-même. Si vous enregistrez aussi les versions (depuis la CI ou le tableau de bord), Keyright peut indiquer aux clients la version la plus récente qu’ils peuvent utiliser, prévisualiser pour combien de clients une version est couverte, et dater les correctifs pour vous.

Quel modèle de couverture choisir

Six préréglages en langage clair couvrent les modèles d’affaires courants. Choisissez-en un par édition ; il se développe en conditions sur chaque licence que vous émettez à partir de ce moment.

PréréglageCe que le client obtientUsage typique
Lifetime updates (mises à jour à vie)Toutes les versions, pour toujours.Un achat unique qui promet des mises à niveau gratuites.
Updates for a period (mises à jour pendant une période)Toute version publiée dans les N mois suivant l’achat ; la copie continue de fonctionner pour toujours.La licence perpétuelle classique avec « un an de mises à jour ».
One major version (une version majeure)Toutes les publications d’une ligne majeure (p. ex. toutes les 2.x), pour toujours.Tarification de mise à niveau par version majeure.
One major + upgrade protection (une majeure + protection de mise à niveau)Une majeure pour toujours, plus tout ce qui est publié dans les N mois — y compris la majeure suivante si elle sort à temps.Tarification par majeure avec plan de maintenance.
Subscription (abonnement)Tout ce qui est publié tant que l’abonnement est actif ; rien après son échéance.Licences de bureau à la manière du SaaS.
Subscription with fallback (abonnement avec repli)Comme Subscription, mais après l’échéance le client conserve les versions publiées pendant qu’il payait.Des abonnements qui ne doivent jamais laisser un client sur le carreau.

Tout le reste — une seule version figée, une ligne mineure, une fenêtre sur mesure — est une condition personnalisée que vous ajoutez à une licence depuis la section Advanced (Avancé) de son onglet Coverage (ou via l’API grants ci-dessous).

Lifetime updatesversiondate de publicationtoutes les versions, pour toujoursUpdates for a periodachat+ N moisfenêtreversions ultérieuresOne major versiontoutes les 2.x, quelle que soit la date3.x1.xOne major + upgrade protectiontoutes les 2.x3.0 si elle sort dans la fenêtreachat+ N moisSubscriptiondurée payéeéchéancerien aprèsSubscription with fallbackdurée payéeéchéanceconserve ce qui a étépublié avantGrisé = versions couvertes. Une version est couverte si son numéro et sa date d'éligibilité tombent dans une condition grisée.Renouvellements et mises à niveau ajoutent du grisé ; rien de déjà grisé n'est retiré.Règle des correctifsDatez un correctif 2.3.1 comme sa version de base 2.3.0 et il reste dans chaque fenêtre qui couvrait la 2.3.0.
Les six préréglages sous forme de régions sur un plan version × date. Les lignes de versions sont des bandes horizontales ; les fenêtres temporelles sont des bandes verticales ; « une majeure + protection de mise à niveau » est l'union des deux.

Mises à jour à vie (Lifetime updates)

Scénario. Vous vendez une clé à l’achat unique et promettez des mises à niveau gratuites à vie (ou vous ne contrôlez tout simplement pas les mises à jour et voulez que le portail le dise). Rien n’expire jamais.

Conditions. Une condition ouverte : toutes les versions, toutes les dates. Ce que le client lit (portail, SDK, API) : « Votre licence couvre toutes les versions. » — un état non couvert ne peut jamais survenir.

Configuration. Politique de l’édition → Lifetime updates (aucune question), ou sur une licence Change coverage… → Lifetime updates. API : {"preset":"lifetime"}.

Mises à jour pendant une période (Updates for a period)

Scénario. Une licence perpétuelle avec douze mois de mises à jour. Le client conserve sa copie pour toujours et peut installer tout ce qui est publié dans la fenêtre — y compris une nouvelle majeure si elle sort à temps. Une version publiée après la fenêtre est « non couverte — fenêtre échue », l’application propose donc Renew (renouveler).

Conditions. Une fenêtre de dates allant de la date d’achat à l’achat + N mois, toute version. Le renouvellement ajoute une autre fenêtre (contiguë, ou avec un intervalle que vous pouvez choisir de laisser non couvert). Le client lit : « Votre licence couvre toute version publiée jusqu’au 9 oct. 2027. »

Configuration. Politique de l’édition → Updates for a period → Updates for (months after purchase) (mises à jour pendant N mois après l’achat). API : {"preset":"updatesForPeriod","periodMonths":12}.

Une version majeure (One major version)

Scénario. Tarification par majeure : une licence 2.x exécute toutes les versions 2.x jamais publiées, y compris celles sorties des années plus tard, mais pas la 3.0. Un build 3.0 est « non couvert — hors de la plage de versions », l’application propose donc Upgrade (mettre à niveau). Les lignes plus anciennes (1.x) sont toujours autorisées — le droit de rétrogradation est intégré.

Conditions. Une plage de versions [major.0.0, major+1.0.0), toute date. Le client lit : « Votre licence couvre toutes les versions d’Acme Studio 2. »

Configuration. Politique de l’édition → One major version → Which major version? (quelle version majeure ?). API : {"preset":"oneMajor","major":2}. Lorsque votre édition vend « la majeure courante », incrémentez la majeure de la politique quand vous publiez la suivante — les clients existants conservent la ligne qu’ils ont achetée.

Une majeure + protection de mise à niveau (One major + upgrade protection)

Scénario. Tarification par majeure plus plan de maintenance : le client possède la 2.x pour toujours et obtient tout ce qui est publié dans les N mois suivants — ainsi, si la 3.0 sort dans la fenêtre, il l’obtient, de même que tout correctif 3.x publié avant la fermeture de la fenêtre. Une version 3.x datée après la fenêtre est hors des deux conditions (→ Renew, puisque c’est la fenêtre qui a expiré). C’est le modèle « mises à niveau payantes avec filet de sécurité » le plus courant.

Conditions. Deux conditions — la ligne majeure, et une fenêtre de dates allant de l’achat à l’achat + N mois. Le client lit : « Votre licence couvre toutes les versions d’Acme Studio 2, plus toute version publiée jusqu’au 9 oct. 2027. »

Configuration. Politique de l’édition → One major + upgrade protection → majeure + mois. API : {"preset":"oneMajorPlusUpgradeProtection","major":2,"periodMonths":12}.

Abonnement (Subscription)

Scénario. Le client peut utiliser toute version publiée tant qu’il paie. La licence elle-même porte normalement une expiration alignée sur la période de facturation (l’application s’arrête donc à la fin de l’abonnement) ; la couverture ajoute la dimension date de publication pour que, par exemple, un client qui rétrograde vers un plan moins cher ou dont la durée est prolongée sans nouvelle clé obtienne toujours exactement les versions auxquelles il a droit.

Conditions. Une fenêtre de dates allant de l’émission à l’émission + N mois. Chaque renouvellement ajoute la fenêtre suivante. Après une échéance, les versions datées après la fenêtre sont « fenêtre échue » → Renew. Le client lit : « Votre licence couvre toute version publiée jusqu’au 9 oct. 2027. »

Configuration. Politique de l’édition → Subscription → Term (months) (durée en mois). API : {"preset":"subscription","periodMonths":12}.

Abonnement avec repli (Subscription with fallback)

Scénario. Un abonnement qui ne laisse jamais un client sur le carreau : lorsqu’il cesse de payer, il conserve les versions publiées pendant qu’il payait (un « repli perpétuel »), et seules les versions postérieures à l’échéance nécessitent un renouvellement.

Conditions. À l’émission, ce préréglage se développe exactement comme Subscription — une fenêtre pour la durée payée. Le repli consiste simplement à ne pas révoquer cette fenêtre à l’échéance : comme les conditions ne font que croître, un client dont l’abonnement prend fin détient toujours une condition couvrant tout ce qui a été publié pendant celui-ci. Lorsque vous traitez une échéance dans votre flux de facturation, ne révoquez pas la condition de couverture ; si vous laissez aussi la licence elle-même expirer, l’application suit votre comportement d’expiration habituel. Pour consigner l’échéance explicitement, ajoutez une condition personnalisée avec la raison Fallback (repli) se terminant à la date d’échéance (voir conditions personnalisées).

Configuration. Politique de l’édition → Subscription with fallback → Term (months). API : {"preset":"subscriptionWithFallback","periodMonths":12}.

Bêtas et accès anticipé

Une condition sans canal ne couvre que les versions stables. Pour permettre à un client d’exécuter des builds bêta, donnez-lui une condition sur le canal beta (activez Include beta builds (inclure les builds bêta) sur la politique, ou définissez "channel":"beta" sur une condition personnalisée) et compilez vos préversions avec KeyrightCoverageChannel=beta. Keyright couvre les bêtas ; il ne fait jamais expirer les builds — si vous voulez qu’une préversion s’auto-expire, intégrez-y KeyrightCoverageBuildExpiry et vérifiez CoverageBuildInfo.Current.BuildExpiryUtc dans votre propre code de démarrage.

Configuration dans le tableau de bord

Tout ce qui suit se trouve sous Releases & coverage (Versions et couverture) — dans les vues Intermediate (Intermédiaire) et Advanced (Avancé) du tableau de bord ; la section apparaît aussi dans la vue Basic (Basique) dès que vous enregistrez une version ou sauvegardez une politique — voir Tableau de bord d’administration → Niveau d’expérience.

1. Enregistrer les versions (facultatif, recommandé)

Enregistrer une version consigne son numéro de version, sa date de publication, une date d’éligibilité distincte facultative et son canal. Rien d’autre — ni fichiers ni URL. Avec un registre, les clients voient la version la plus récente qu’ils peuvent utiliser, le tableau de bord prévisualise pour qui chaque version est couverte, et le vérificateur de couverture peut dater une version pour vous.

Releases & coverage → Releases : un tableau des versions enregistrées avec leur date de publication, la date à laquelle elles comptent comme publiées (un correctif 2.3.1 daté comme sa version de base) et un badge « couverte pour » par version, par exemple couverte pour 4 sur 5 · 1 non couverte ; une carte latérale affiche la commande CLI keyright release --register pour la CI.
Le registre des versions — versions, dates, canal, et pour combien de vos licences actuelles chacune est couverte.

Depuis la CI, enregistrez chaque version au moment de sa publication :

# KEYRIGHT_URL et KEYRIGHT_ADMIN_TOKEN peuvent aussi être passés via --url / --admin-token
keyright release --register --product acme-studio --version 2.4.0 --release-date 2026-10-01

# un correctif qui doit rester couvert pour tous ceux qui avaient la 2.3.0 : datez-le comme la version de base
keyright release --register --product acme-studio --version 2.3.1 --release-date 2026-09-30 --entitlement-date 2026-07-02

# un build de préversion sur le canal bêta
keyright release --register --product acme-studio --version 3.0.0 --release-date 2026-10-08 --channel beta

Ou via l’API (idempotente par produit + version) :

curl -X POST $BASE/admin/products/acme-studio/releases -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
  -d '{"version":"2.3.1","releaseDate":"2026-09-30","entitlementDate":"2026-07-02","channel":"stable"}'
# -> { "product":"acme-studio", "version":"2.3.1", "releaseDate":"2026-09-30", "entitlementDate":"2026-07-02", "channel":"stable" }

# combien de licences couvriraient une version, avant de la publier
curl "$BASE/admin/products/acme-studio/releases/preview?version=2.5.0&on=2026-10-09" -H "X-Admin-Token: $TOKEN"
# -> { "product":"acme-studio", "version":"2.5.0", "covered":4, "notCovered":1, "notConfigured":0, "total":5 }

La boîte de dialogue Register release (enregistrer une version) affiche la même prévisualisation en direct pendant que vous saisissez.

La boîte de dialogue Register release : version 2.5.0, des sélecteurs de date « released on » et « counts as released on » avec une indication d'utiliser la date de la version de base pour un correctif, un sélecteur de canal, et une prévisualisation en direct « pour qui cette version est couverte » avec le nombre de licences couvertes, non couvertes et le total.
L'enregistrement d'une version prévisualise, avant que vous ne publiiez, pour combien de clients existants elle est couverte.

2. Donner à chaque édition une politique de couverture

Ouvrez Releases & coverage → Coverage policy (politique de couverture), choisissez l’édition (tier), sélectionnez un préréglage, répondez à sa question ou à ses deux questions, puis cliquez sur Save policy (enregistrer la politique). Le panneau latéral prévisualise, en langage clair, ce qu’un acheteur obtient, la phrase qu’il lira, et le message qu’il verrait s’il ouvrait une version hors de ses conditions.

Releases & coverage → Coverage policy pour l'édition pro : six cartes de préréglages avec One major + upgrade protection sélectionné et un badge « Policy in effect » ; les questions « Which major version? 2 » et « Updates for 12 months » ; les interrupteurs de valeurs par défaut raisonnables ; une prévisualisation en direct expliquant ce qu'un acheteur obtient, ce qu'il lira, et le message s'il ouvre la 4.0.0 plus tard ; et les sections Advanced pour le comportement en cas de version non couverte et l'application de la politique aux clients existants.
La politique de couverture d'une édition : un préréglage, ses questions, et une prévisualisation en direct de ce que le client obtient et lit.

Signification de chaque contrôle :

ContrôleSignification
Edition (édition)Les politiques sont définies par édition. Les nouvelles licences de cette édition reçoivent ces conditions à l’émission ; les autres éditions ne sont pas touchées.
Preset (préréglage)L’un des six modèles ci-dessus.
Which major version? (quelle version majeure ?)Pour les deux préréglages « une majeure » : la ligne couverte par la licence (2 → toutes les 2.x).
Updates for / Term (months) (mises à jour pendant / durée en mois)Pour les préréglages fondés sur les dates : la longueur de la fenêtre, comptée à partir du jour d’émission de chaque licence.
Downgrade rights (droit de rétrogradation)Documente que les versions plus anciennes sont toujours autorisées — une ligne de versions inclut déjà tout ce qui se trouve en dessous, et une fenêtre inclut tout ce qui a été publié avant elle.
Hotfixes keep their base release’s date (les correctifs conservent la date de leur version de base)Documente la règle de la date d’éligibilité : datez un correctif comme sa version de base lorsque vous l’enregistrez et il reste couvert.
Late renewals cover the gap (les renouvellements tardifs couvrent l’intervalle)Documente votre façon de renouveler : une condition de renouvellement qui commence à l’ancienne date de fin couvre l’intervalle ; une qui commence aujourd’hui le laisse non couvert.
Include beta builds (inclure les builds bêta)Place les conditions de la politique sur le canal beta au lieu du canal stable uniquement.
Advanced: what happens when a customer runs a version they don’t have (Avancé : que se passe-t-il quand un client exécute une version qu’il ne possède pas)Consigne le comportement que vous avez choisi pour votre application — informer, exécuter en lecture seule, ou bloquer. Le serveur renvoie la décision ; votre application met en œuvre le comportement (voir Dans votre application).
Advanced: apply this policy to existing customers (Avancé : appliquer cette politique aux clients existants)Une migration prévisualisée pour les licences émises avant cette politique — voir ci-dessous.

Enregistrer une politique n’affecte que les licences émises à partir de maintenant. La même chose via l’API — notez qu’un upsert d’édition remplace l’édition entière, envoyez donc aussi ses sièges et ses fonctionnalités :

curl -X POST $BASE/admin/products/acme-studio/tiers -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
  -d '{"name":"pro","seats":5,"entitlements":{"export":"true"},
       "coveragePolicy":{"preset":"oneMajorPlusUpgradeProtection","major":2,"periodMonths":12}}'

# effacer une politique : envoyez un préréglage vide (omettre coveragePolicy laisse la politique actuelle inchangée)
curl -X POST $BASE/admin/products/acme-studio/tiers -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
  -d '{"name":"pro","seats":5,"entitlements":{"export":"true"},"coveragePolicy":{"preset":""}}'

preset est l’un de lifetime, updatesForPeriod, oneMajor, oneMajorPlusUpgradeProtection, subscription, subscriptionWithFallback ; major est requis par les deux préréglages « une majeure » et periodMonths par les quatre préréglages fondés sur les dates ; channel est facultatif (beta).

3. Émettre les licences comme d’habitude

Rien ne change dans votre façon d’émettre : Licenses → Issue license (Licences → Émettre une licence) (ou POST /admin/licenses, ou le webhook de traitement des commandes de votre boutique). À l’émission, la politique de l’édition se développe en conditions sur la nouvelle licence. Le volet affiche les fonctionnalités de l’édition ; la couverture que porte la licence est visible ensuite sur sa page.

Le volet Issue license par-dessus la liste Licenses : produit Acme Studio, un sélecteur d'édition (tier) affichant l'édition et ses sièges, une ligne « This edition includes », le titulaire, l'e-mail de contact, les sièges, la durée, un interrupteur d'essai et une section Advanced repliée pour les surcharges de fonctionnalités, les notes et une clé personnalisée.
L'émission d'une licence est inchangée — la politique de couverture de l'édition est appliquée automatiquement à la nouvelle licence.

4. Voir et modifier la couverture d’une licence

La page d’une licence gagne un onglet Coverage (Couverture) (il apparaît automatiquement dès que la licence a une couverture, quelle que soit votre vue du tableau de bord). Il affiche la phrase destinée au client, la politique en vigueur, la version enregistrée la plus récente qu’il peut utiliser, si l’application des règles est active, un vérificateur de couverture par licence, et un historique en langage clair de chaque condition que la licence a jamais eue.

L'onglet Coverage d'une licence : « What the customer sees — Your license covers all Acme Studio 2 versions, plus any new version released through Oct 9, 2027 », la politique One major + upgrade protection, la version la plus récente utilisable 2.4.0, l'application des règles Enforced, un vérificateur de couverture intégré, l'historique de couverture, un bouton Change coverage…, et des cartes latérales listant les versions enregistrées et expliquant le fonctionnement de la couverture.
L'onglet Coverage d'une licence : la phrase que lit le client, les faits qui la sous-tendent, un vérificateur et l'historique.

Change coverage… (modifier la couverture) applique un préréglage à cette licence — c’est ainsi que vous traitez un renouvellement (« Updates for a period » à nouveau), une mise à niveau (« One major version » 3) ou une conversion en abonnement. Ici, la couverture ne peut que croître : les nouvelles conditions s’ajoutent à côté des conditions existantes.

La boîte de dialogue Change coverage d'une licence : les six cartes de préréglages avec Subscription with fallback sélectionné, un champ Term (months) réglé sur 12, les interrupteurs de valeurs par défaut raisonnables, et une prévisualisation en direct de ce que l'acheteur obtient.
Change coverage… sur une licence — les mêmes préréglages, appliqués à ce seul client. Rien de ce qu'il peut utiliser aujourd'hui ne lui est retiré.
# appliquer un préréglage à une licence (renouvellement / mise à niveau / conversion)
curl -X POST $BASE/admin/licenses/LIC-…/coverage/preset -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
  -d '{"preset":"updatesForPeriod","periodMonths":12}'

# lire la couverture d'une licence : ce qui est en vigueur, le numéro de séquence et le registre complet
curl $BASE/admin/licenses/LIC-…/coverage -H "X-Admin-Token: $TOKEN"
# -> { "configured":true, "mode":"enforced", "seq":3, "supportUntilUtc":"2027-10-09T00:00:00.0000000Z",
#      "activeGrants":[ { "id":"…", "min":"2.0.0", "maxExcl":"3.0.0", "dates":[], "channel":null,
#                         "reason":"purchase", "sourceRef":null, "createdUtc":"…", "active":true }, … ],
#      "ledger":[ … chaque condition jamais ajoutée, avec "active":false pour celles qui ont été retirées … ] }

Conditions personnalisées et registre technique

La section repliée Advanced: coverage history (technical) (Avancé : historique de couverture (technique)) est destinée au personnel de support. Chaque ligne est une condition avec sa plage de versions, ses dates, son canal, sa raison et son état ; depuis cette section vous pouvez ajouter une condition personnalisée (une version figée, une ligne mineure, une fenêtre avec des dates explicites, un canal beta), retirer une condition active, suspendre / reprendre l’application des règles pour cette licence, et télécharger le fichier de couverture signé.

La section Advanced: coverage history (technical) ouverte sur une licence : un badge Enforced avec un bouton Pause enforcement, un tableau de conditions (versions 2.0.0 → < 3.0.0 avec toutes dates, et toute version avec les dates Oct 9 2026 – Oct 9 2027, toutes deux stable, Purchase, Active, chacune avec un bouton Remove), les boutons Add a custom term et Download signed coverage file, et « Coverage version 2 ».
Le registre technique : une ligne par condition. Retirer une condition réduit la couverture et est consigné ; « Coverage version » est le numéro de séquence que l'application utilise pour refuser les blocs plus anciens rejoués.
# ajouter une condition personnalisée : tout ce qui est publié dans une fenêtre, sur le canal bêta, consigné comme promotion
curl -X POST $BASE/admin/licenses/LIC-…/coverage/grants -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
  -d '{"minVersion":null,"maxExclVersion":null,"dates":[{"from":"2026-10-01","to":"2026-12-31"}],
       "channel":"beta","reason":"promo","sourceRef":"launch-week"}'
# une version figée : {"minVersion":"2.4.1","maxExclVersion":"2.4.2"}  — une ligne mineure : {"minVersion":"2.4.0","maxExclVersion":"2.5.0"}

# retirer une condition active (consigné, jamais supprimé)
curl -X POST $BASE/admin/licenses/LIC-…/coverage/revoke -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
  -d '{"grantId":"<id from the ledger>","sourceRef":"refund #4821"}'

# suspendre l'application des règles pour une licence (le bloc suivant est marqué unenforced → toutes les versions s'exécutent) ; "enforced" la reprend
curl -X POST $BASE/admin/licenses/LIC-…/coverage/mode -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
  -d '{"mode":"unenforced"}'

reason est l’un de purchase, renewal, upgrade, promo, fallback, migration, legacy ; sourceRef est un texte libre pour votre piste d’audit (un identifiant de commande, un ticket). Les plages de versions ne comparent que major.minor.patch — les suffixes de préversion sont ignorés, donc 2.5.0-beta est dans 2.x et 3.0.0-rc1 n’y est pas. Les dates sont des dates calendaires comparées de façon inclusive, sans conversion de fuseau horaire.

5. Vérifier n’importe quelle version pour n’importe quelle licence

Le vérificateur de couverture — dans l’onglet Coverage checker du hub et dans l’onglet Coverage de chaque licence — répond à « cette licence peut-elle utiliser la version X ? » exactement comme le feraient l’application et votre page de téléchargement, avec une raison en une ligne et, lorsque le registre est renseigné, la version la plus récente qu’elle peut utiliser.

Releases & coverage → Coverage checker : une clé de licence, la version 3.0.0 et une date de publication facultative, avec le résultat « Not covered. That version is outside the version line this license includes. » et un lien Open vers la licence.
Le vérificateur donne la même réponse que l'application et l'API d'éligibilité.
curl "$BASE/admin/licenses/LIC-…/coverage/check?version=3.0.0" -H "X-Admin-Token: $TOKEN"
# -> { "status":"NotCovered", "reason":"OutsideVersionRange", "newestCovered":"2.4.0" }
# ajoutez &on=2026-11-01 pour évaluer à une date d'éligibilité explicite (par défaut : la date enregistrée de la version, sinon aujourd'hui)
# ajoutez &channel=beta pour vérifier un build de préversion

Appliqué, non appliqué et « non configuré »

Chaque bloc de couverture porte un mode :

  • Appliqué (enforced) — l’état normal d’une licence qui a des conditions de couverture : l’application compare le build en cours d’exécution aux conditions.
  • Non appliqué (unenforced) — le bloc dit « traitez toutes les versions comme couvertes ». Keyright émet des blocs non appliqués lorsque vous suspendez l’application des règles sur une licence, et — automatiquement, après une période de grâce — lorsque le plan d’un espace de travail ou une licence auto-hébergée n’inclut plus la couverture. Vos clients ne sont jamais verrouillés à cause de votre état de facturation ; lorsque le droit revient, les blocs appliqués reprennent avec les conditions intactes.
  • Non configuré — une licence qui n’a jamais eu de conditions de couverture (une licence plus ancienne, ou un espace de travail qui n’utilise pas la couverture) reçoit un bloc signé « non configuré ». Le SDK le signale comme NotConfigured et traite la licence comme sans restriction. Cet état ne provient jamais que d’un bloc signé : un bloc manquant n’est jamais lu comme « sans restriction », si bien que retirer le bloc d’une machine n’est pas un moyen de contourner l’application des règles.

L’application des règles dans votre application est une décision prise à la compilation (KeyrightRequireCoverage, ci-dessous). Les builds sans cette propriété ignorent entièrement la couverture ; les builds qui la portent interrogent Keyright, rafraîchissent une fois avant de refuser, et échouent en mode fermé vers un état « importez un fichier de licence à jour » lorsqu’ils sont hors ligne sans rien en cache.

Appliquer une politique aux clients existants

Enregistrer une politique ne touche jamais les licences déjà émises. Lorsque vous voulez que les clients existants gagnent quelque chose — vous avez introduit la protection de mise à niveau et souhaitez l’étendre à tous les clients en 2.x, par exemple — utilisez Advanced: apply this policy to existing customers (Avancé : appliquer cette politique aux clients existants) dans l’onglet de la politique. Saisissez une version de référence, cliquez sur Preview who is affected… (prévisualiser qui est concerné) pour voir combien de licences gagneraient la couverture pour cette version (et combien l’ont déjà), puis Apply (appliquer). La couverture ne peut que croître : personne ne perd une version qu’il peut utiliser aujourd’hui.

# prévisualisation : combien de licences acme-studio couvriraient nouvellement la 3.0.0 si tout le monde recevait une condition 2.x + fenêtre
curl -X POST "$BASE/admin/products/acme-studio/coverage/migrate?dryRun=true&version=3.0.0" \
  -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
  -d '{"minVersion":null,"maxExclVersion":null,"dates":[{"from":"2026-10-09","to":"2027-10-09"}],"reason":"migration","sourceRef":"upgrade-protection-rollout"}'
# -> { "total":5, "coveredBefore":1, "coveredAfter":5, "gain":4, "applied":false }

# appliquer (dryRun=false) : la même condition est ajoutée à chaque licence non révoquée du produit

Licences héritées : rattrapage (backfill) avant d’appliquer les règles

Une licence émise avant que vous n’adoptiez la couverture n’a aucune condition. Si vous livriez un build RequireCoverage à un tel client sans rien faire, il serait invité à importer un fichier de licence. L’onglet Enforcement readiness (préparation à l’application des règles) existe pour que cela n’arrive jamais : il compte les licences qui n’ont toujours pas de conditions et bloque la liste de contrôle tant qu’elles n’ont pas été rattrapées.

Releases & coverage → Enforcement readiness : des tuiles pour 5 licences, 4 avec des conditions de couverture, 1 sans aucune condition ; une liste de contrôle (politiques rédigées, l'application livre RequireCoverage, les licences ont des conditions) ; un avertissement indiquant qu'une licence a d'abord besoin de conditions ; les boutons de rattrapage Treat them as lifetime, Use each license's expiry date, Import coverage dates (CSV) ; un bouton Export offline license files ; et une carte latérale avec les propriétés MSBuild à définir dans le build.
Enforcement readiness avant le rattrapage : une licence héritée n'a toujours pas de conditions, la page le signale et propose les trois façons d'y remédier.

Choisissez l’un des trois rattrapages — chacun donne à la licence une condition marquée existing customer (client existant) (reason: legacy) :

RattrapageRésultatQuand
Treat them as lifetime (les traiter comme à vie)Chaque licence héritée couvre toutes les versions, pour toujours.Vous n’avez jamais facturé les mises à jour auparavant.
Use each license’s expiry date (utiliser la date d’expiration de chaque licence)Une fenêtre se terminant à la date d’expiration propre à la licence (les licences perpétuelles reçoivent la couverture à vie).Des licences à durée déterminée dont le support prenait fin avec la licence.
Import coverage dates (CSV) (importer les dates de couverture)Des dates de fin de support par licence depuis un fichier licenseId,supportUntil ; une date vide signifie à vie.Vous suiviez les fenêtres de support ailleurs.
curl $BASE/admin/products/acme-studio/coverage/readiness -H "X-Admin-Token: $TOKEN"
# -> { "total":5, "configured":4, "missing":1 }

# rattraper chaque licence qui n'a toujours pas de conditions (defaultRule : "lifetime" ou "supportEnd")
curl -X POST $BASE/admin/products/acme-studio/coverage/backfill -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
  -d '{"defaultRule":"lifetime"}'
# -> { "backfilled":1 }

# ou par licence (l'import CSV envoie la même structure)
curl -X POST $BASE/admin/products/acme-studio/coverage/backfill -H "X-Admin-Token: $TOKEN" -H "content-type: application/json" \
  -d '{"rows":[{"licenseId":"LIC-AAAA","supportUntil":"2027-03-12"},{"licenseId":"LIC-BBBB","supportUntil":null}]}'

Fixez les bonnes dates avant de livrer un build qui applique les règles : une fois le bloc signé d’un client livré, il peut être étendu mais pas véritablement repris. Les clients en ligne récupèrent leurs conditions rattrapées au prochain rafraîchissement ; les clients isolés (air-gapped) ont besoin de l’export ci-dessous.

Enforcement readiness après le rattrapage : 5 licences, 5 avec des conditions de couverture, 0 sans ; la liste de contrôle indique que chaque licence a des conditions de couverture et un encart vert « Ready » annonce qu'un build avec RequireCoverage ne verrouillera jamais un client.
Après le rattrapage : chaque licence porte des conditions et le produit est prêt pour un build qui applique les règles.

Clients isolés (air-gapped)

Une machine qui n’atteint jamais votre service ne peut pas recevoir le bloc à l’activation. Livrez-le sous forme de fichier à la place :

  • Une licence : le bouton Download signed coverage file (télécharger le fichier de couverture signé) dans son onglet Coverage (ou GET /admin/licenses/{id}/coverage/block → { "block": "…" }).
  • Toutes les licences d’un produit : Export offline license files (exporter les fichiers de licence hors ligne) dans l’onglet Enforcement readiness (ou GET /admin/products/{slug}/coverage/export → { "product", "count", "items": [ { "licenseId", "coverageBlock" } ] }).

Le fichier est l’enveloppe du bloc signé — du JSON avec un payload et une sig. Remettez-le au client avec son fichier de licence hors ligne ou sa lease ; son application l’importe avec ImportCoverageBlock (ci-dessous). Un renouvellement pour un client isolé (air-gapped) est simplement un fichier plus récent : il porte un numéro de séquence plus élevé et remplace donc celui en cache, tandis qu’un fichier plus ancien est refusé. L’export respecte votre droit exactement comme le fait l’activation — des blocs non appliqués si l’espace de travail n’inclut plus la couverture — il ne peut donc jamais verrouiller qui que ce soit.

Ce que voit le client

Dans le portail client

La page de licence du portail affiche la phrase de couverture — formulée en termes de possession, sans jargon interne — juste sous le résumé de la licence, et la liste Devices (appareils) en dessous. Les clients savent ce qu’ils possèdent sans ouvrir de ticket de support.

Le portail client sur un téléphone : la carte de licence pour acme-studio affiche pro · 2 of 5 devices in use · never expires et la phrase de couverture « Your license covers all acme-studio 2 versions, plus any version released through 9 Oct 2027 », suivie de l'encadré de la clé de licence, de la liste des appareils avec des boutons Free seat, et d'une section More options repliée.
Le portail énonce la couverture en une phrase. La même phrase est disponible pour votre propre portail ou votre application.

Si vous exploitez votre propre portail client, l’API du portail (en session authentifiée) renvoie la même phrase et la date de fin de support dérivée : GET /portal/license/{id}/coverage → { "status": "Your license covers …", "supportUntilUtc": "2027-10-09T00:00:00.0000000Z" } (null lorsque la couverture n’est pas bornée par une date).

Les phrases et messages par défaut

Voici les chaînes que produisent le SDK et l’API (le tableau de bord les prévisualise avec le même sens). Toutes peuvent être remplacées état par état — voir messages ci-dessous.

ÉtatTexte par défaut
À vieVotre licence couvre toutes les versions. (Your license covers every version.)
Une majeure ({line} = « Acme Studio 2 »)Votre licence couvre toutes les versions d’Acme Studio 2. (Your license covers all Acme Studio 2 versions.)
Fenêtre (période / abonnement)Votre licence couvre toute version publiée jusqu’au 9 oct. 2027. (Your license covers any version released through 9 Oct 2027.)
Majeure + protection de mise à niveauVotre licence couvre toutes les versions d’Acme Studio 2, plus toute version publiée jusqu’au 9 oct. 2027. (Your license covers all Acme Studio 2 versions, plus any version released through 9 Oct 2027.)
Fenêtre ouverteVotre licence couvre toutes les versions publiées à ce jour. (Your license covers every version released so far.)
Non couverte — fenêtre échueCette version (2.5.0) a été publiée après la fin de vos mises à jour le 9 oct. 2027. Vous pouvez continuer à utiliser la 2.4.0. Renouvelez pour utiliser la 2.5.0. (This version (2.5.0) was released after your updates ended on 9 Oct 2027. You can keep using 2.4.0. Renew to use 2.5.0.) (sans registre, la phrase « continuer à utiliser » est omise) → action Renew
Non couverte — hors de la plage de versionsCette version (3.0.0) n’est pas incluse dans votre licence Acme Studio 2. Vous pouvez continuer à utiliser la 2.4.0. Mettez à niveau pour utiliser la 3.0.0. (This version (3.0.0) isn’t included in your Acme Studio 2 license. You can keep using 2.4.0. Upgrade to use 3.0.0.) → action Upgrade
Indisponible hors ligneAcme Studio n’a pas pu confirmer hors ligne que votre licence inclut cette version. Connectez-vous à Internet, ou importez un fichier de licence à jour depuis Acme Software. (Acme Studio couldn’t confirm that your license includes this version while offline. Connect to the internet, or import an updated license file from Acme Software.) → action Import license file

Dans votre application (SDK .NET)

La couverture a besoin de deux choses de votre build : de quelle version il s’agit (pour pouvoir la comparer aux conditions) et s’il doit appliquer les règles. Les deux sont des propriétés de compilation, de sorte qu’un binaire livré ne peut pas en être détourné à l’exécution.

1. Embarquer l’identité de version du build (MSBuild)

Le paquet Keyright.NET fournit des cibles MSBuild qui lisent ces propriétés et les compilent dans l’assembly sous forme de métadonnées que le SDK relit au démarrage :

<!-- dans le .csproj de l'application, ou Directory.Build.props — normalement définis depuis votre pipeline de publication -->
<PropertyGroup>
  <KeyrightCoverageVersion>2.4.0</KeyrightCoverageVersion>                 <!-- la version produit de ce build (major.minor.patch) -->
  <KeyrightCoverageEntitlementDate>2026-10-01</KeyrightCoverageEntitlementDate>  <!-- la date à laquelle la couverture est jugée (yyyy-MM-dd) -->
  <KeyrightCoverageChannel>stable</KeyrightCoverageChannel>               <!-- stable (par défaut) ou beta -->
  <KeyrightRequireCoverage>true</KeyrightRequireCoverage>                 <!-- intégrer l'application des règles dans ce build -->
  <!-- facultatif : une auto-expiration stricte pour les builds de préversion, relue comme CoverageBuildInfo.BuildExpiryUtc -->
  <!-- <KeyrightCoverageBuildExpiry>2027-01-01T00:00:00Z</KeyrightCoverageBuildExpiry> -->
</PropertyGroup>

Depuis la CI, passez-les en ligne de commande pour que le pipeline soit la source unique de vérité :

dotnet publish -c Release \
  -p:KeyrightCoverageVersion=2.4.0 \
  -p:KeyrightCoverageEntitlementDate=2026-10-01 \
  -p:KeyrightRequireCoverage=true

Règles que les cibles font respecter et sur lesquelles le SDK s’appuie :

  • Un build KeyrightRequireCoverage=true doit définir KeyrightCoverageEntitlementDate ; sinon la compilation échoue avec l’erreur KR0001 (il n’y aurait rien à évaluer). Les propriétés non définies n’émettent aucune métadonnée.
  • Un correctif hérite de la date d’éligibilité de sa version de base. Compilez la 2.3.1 avec KeyrightCoverageEntitlementDate=2026-07-02 (la date de la 2.3.0) et elle reste couverte pour tous ceux qui avaient la 2.3.0 — et enregistrez-la avec le même --entitlement-date pour que le vérificateur et le registre soient d’accord.
  • Les suffixes de préversion (2.5.0-beta.1) sont ignorés pour la correspondance des plages ; utilisez le canal pour les bêtas.

À l’exécution, les valeurs embarquées sont disponibles via CoverageBuildInfo.Current — Version, EntitlementDate, Channel, BuildExpiryUtc, RequireCoverage et HasMetadata.

2. Vérifier la couverture au démarrage

Le SDK fait déjà la moitié du travail : chaque ActivateAsync (et chaque rafraîchissement de lease) envoie la version et la date d’éligibilité du build, reçoit le bloc de couverture signé avec la lease, le vérifie avec votre clé publique embarquée et le met en cache à côté de la licence. Vous ajoutez un seul appel :

using Keyright.Client;
using Keyright.Coverage;

static readonly KeyrightClient License = KeyrightClient.Initialize(new KeyrightOptions
{
    Product         = "acme-studio",
    PublicKeyBase64 = "MIIBIjANBgkq...",              // la même clé vérifie les licences, les leases ET les blocs de couverture
    ServiceUrl      = "https://licensing.acme.example",
});

static async Task<bool> GateOnCoverageAsync(CancellationToken ct)
{
    var build = CoverageBuildInfo.Current;
    if (!build.RequireCoverage) return true;          // un build qui n'applique pas les règles ignore entièrement la couverture

    // Décide à partir du bloc en cache ; si non couvert (ou rien en cache), rafraîchit silencieusement la licence une fois
    // (délai court, hors ligne accepté) et décide à nouveau. Ne lève jamais d'exception pour un problème réseau.
    CoverageCheckResult check = await License.CheckCoverageAsync(ct: ct);

    if (check.IsCovered)                              // Covered, ou NotConfigured (la couverture n'impose aucune restriction)
        return true;

    // Non couvert, ou indisponible hors ligne : construire le message + l'action unique et laisser l'application décider.
    var message = BuildMessage(check);
    switch (check.Status)
    {
        case CoverageCheckStatus.UnavailableOffline:  // aucun bloc utilisable et le rafraîchissement a échoué (air-gapped)
            ShowImportLicenseFile(message);           // "…import an updated license file from Acme Software."
            return false;
        case CoverageCheckStatus.NotCovered:
            return ShowNotCovered(message);           // bloquer, exécuter en lecture seule, ou simplement informer — à vous de choisir
        default:
            return true;
    }
}

CheckCoverageAsync(version?, entitlementDate?) donne par défaut à ses deux arguments les métadonnées embarquées du build, de sorte que l’appel sans paramètre répond à « ce build est-il couvert ? ». Il ne lève une exception que s’il n’y a aucune version du tout (ni embarquée ni passée en paramètre). Le résultat :

MembreSignification
StatusCovered, NotCovered, NotConfigured ou UnavailableOffline.
IsCoveredtrue pour Covered et NotConfigured — l’application peut s’exécuter.
ReasonPour NotCovered : WindowLapsed (ses mises à jour ont pris fin avant cette version → renouveler) ou OutsideVersionRange (cette ligne de versions n’est pas la sienne → mettre à niveau).
NewestCoveredLa version couverte la plus récente lorsque le bloc ou le registre la fait connaître (SemVer?), pour « vous pouvez continuer à utiliser la 2.4.0 ».
RefreshedIndique si un rafraîchissement silencieux a été tenté pendant cette vérification.

UnavailableOffline est délibérément distinct de NotCovered : il signifie rien n’est en cache et le service n’a pas pu être joint, ce qui, sur un build appliquant les règles, est une situation « veuillez importer le fichier que nous vous avons envoyé » — jamais « votre licence est invalide », et jamais une invite à saisir la clé.

3. Décider localement, à tout moment

Pour vos propres messages dans l’application ou votre système de mise à jour — « dois-je afficher la bannière 3.0 ? », « quelle est la version la plus récente que ce client peut installer ? » — lisez le bloc en cache sans aucun accès réseau :

CoverageInfo cov = License.GetCoverage();   // depuis le bloc vérifié en cache ; ne contacte jamais un endpoint de distribution
if (cov.Available)
{
    bool notConfigured  = cov.IsNotConfigured;      // bloc signé « aucune restriction »
    CoverageMode mode   = cov.Mode;                 // Enforced / Unenforced
    DateTime? supportTo = cov.SupportUntilUtc;      // fin de la dernière fenêtre, ou null si non borné par une date
    long seq            = cov.Seq;                  // le numéro de séquence du bloc (anti-rollback)

    foreach (Grant g in cov.Grants)                 // les conditions : plage de versions, intervalles de dates, canal, raison
        Console.WriteLine($"{g.Versions?.MinInclusive}..{g.Versions?.MaxExclusive} {g.Reason}");

    CoverageDecision d = cov.Decide(SemVer.Parse("3.0.0"), CalendarDate.Parse("2026-11-01"), channel: null);
}

// En une ligne : cette version est-elle couverte pour cette licence ? La date d'éligibilité et le canal prennent par défaut les métadonnées du build.
bool can30 = License.IsCovered("3.0.0");
bool canOn = License.IsCovered("3.0.0", entitlementDate: "2026-11-01", channel: "beta");

IsCovered renvoie true pour une licence non configurée et false lorsqu’aucun bloc valide n’est disponible — un bloc manquant n’est jamais « couvert ». GetCoverage() renvoie CoverageInfo.Unavailable (Available == false) lorsque la licence elle-même ne se valide pas ou que rien n’est en cache.

4. Les messages et l’action unique

CoverageMessaging transforme un résultat en texte destiné au client et en exactement un appel à l’action, avec vos URL et votre formulation :

static readonly CoverageMessageOptions Wording = new()
{
    Product              = "Acme Studio",
    Vendor               = "Acme Software",
    UpdatesTerm          = "maintenance",            // « updates » par défaut ; ou « support »
    LineTemplate         = "{product} {major}",      // comment se lit une ligne de versions, p. ex. « Acme Studio 2 » ; ou "{major}.x"
    RenewUrl             = "https://acme.example/renew?key={key}",
    UpgradeUrl           = "https://acme.example/upgrade",
    ImportLicenseFileUrl = "https://acme.example/support/offline-license",
    RenewLabel           = "Renew maintenance",
    FormatDate           = d => d.ToString("d MMMM yyyy", new CultureInfo("en-GB")),   // {date} doit se lire sans ambiguïté
};
// Localisez ou réécrivez n'importe quel état : les clés sont les constantes CoverageMessaging.Key*, les modèles peuvent utiliser
// {product} {version} {newestCovered} {line} {date} {vendor} {updatesTerm}
// Wording.TemplateOverrides[CoverageMessaging.KeyWindowLapsed] = "…";

static CoverageMessage BuildMessage(CoverageCheckResult check)
{
    var cov = License.GetCoverage();
    int? lineMajor = cov.Grants.Where(g => g.Versions?.MinInclusive is not null)
                               .Select(g => (int?)g.Versions!.MinInclusive!.Value.Major).Max();
    var ctx = new CoverageMessaging.Context
    {
        Version       = CoverageBuildInfo.Current.Version ?? "",
        LineMajor     = lineMajor,                  // pour {line}
        BoundaryDate  = cov.SupportUntilUtc,         // pour {date} dans « fenêtre échue »
        NewestCovered = check.NewestCovered?.ToString(),
    };
    return CoverageMessaging.Build(check.Status, check.Reason, ctx, Wording);
}

// message.Text        -> "This version (3.0.0) isn't included in your Acme Studio 2 license. You can keep using 2.4.0. Upgrade to use 3.0.0."
// message.Action      -> CoverageAction.Upgrade   (Renew | Upgrade | ImportLicenseFile | None)
// message.ActionUrl   -> "https://acme.example/upgrade"
// message.ActionLabel -> "Upgrade"

La correspondance est fixe, de sorte que l’interface affiche toujours le bon bouton : fenêtre échue → Renew, hors de la plage de versions → Upgrade, indisponible hors ligne → Import license file ; Covered et NotConfigured produisent un message vide avec CoverageAction.None. Pour une ligne d’état dans l’application (« qu’est-ce que je possède ? »), utilisez CoverageMessaging.StatusSentence(cov.Grants, cov.IsNotConfigured, Wording) — la même phrase que celle affichée par le portail.

5. Importer un fichier de couverture (isolé, air-gapped)

string envelopeJson = File.ReadAllText("northwind.coverage.json");   // depuis votre export ou le bouton de téléchargement de la licence
CoverageVerifyResult r = License.ImportCoverageBlock(envelopeJson);    // vérifie, lie à la licence, applique l'anti-rollback ; aucun accès réseau
if (!r.Ok)
{
    // r.Status: Malformed | UnknownKey | BadSignature | RolledBack | WrongBinding | UnsupportedVersion
    ShowImportError(r.Status);
}

Un résultat RolledBack signifie que le fichier est plus ancien que ce que la machine possède déjà (numéro de séquence inférieur) et a été ignoré ; WrongBinding signifie qu’il a été émis pour une autre licence ou un autre produit ; UnknownKey signifie qu’il a été signé par une clé que votre build n’embarque pas — pendant une rotation de clés, conservez l’ancienne clé publique dans AdditionalPublicKeysBase64 exactement comme vous le faites pour les licences et les leases.

6. Contrôler vos propres téléchargements : l’API d’éligibilité

Votre page de téléchargement ou votre système de mise à jour pose la même question à Keyright de serveur à serveur, authentifiée par la clé du client. Keyright répond ; c’est vous qui décidez de servir ou non le fichier.

curl -X POST $BASE/v1/coverage -H "content-type: application/json" \
  -d '{"key":"LIC-…","product":"acme-studio","version":"2.5.0"}'
# -> { "eligible":true, "licenseValid":true, "status":"Covered", "reason":"None", "newestCovered":"2.4.0" }
# facultatif : "on":"2026-11-01" (date d'éligibilité ; par défaut = la date enregistrée, sinon aujourd'hui), "channel":"beta"

eligible vaut licenseValid && (status is Covered or NotConfigured) ; status/reason sont la décision brute, pour que vous puissiez formuler la page vous-même. Depuis un backend .NET :

using var http = new HttpClient { BaseAddress = new Uri("https://licensing.acme.example/") };
var resp = await http.PostAsJsonAsync("v1/coverage", new { key, product = "acme-studio", version = "2.5.0" });
var e = await resp.Content.ReadFromJsonAsync<Eligibility>();
if (e is { Eligible: true }) ServeDownload("2.5.0"); else ShowWhy(e!.Status, e.Reason, e.NewestCovered);

record Eligibility(bool Eligible, bool LicenseValid, string Status, string Reason, string? NewestCovered);

Référence des comportements

SituationCe qui se passe
Le client renouvelle / met à niveauVous ajoutez des conditions (tableau de bord ou API). L’application les récupère à son prochain lancement ou rafraîchissement — sans nouvelle saisie de clé, sans réactivation.
Le client revient à un build plus ancienToujours couvert : une version plus ancienne est dans chaque ligne de versions et avant chaque fenêtre.
Deux versions côte à côte, ou des versions différentes par machineChaque build s’évalue lui-même par rapport à la même licence ; les sièges restent par machine, comme auparavant.
Horloge reculéeLa couverture compare la date d’éligibilité signée du build aux conditions, jamais l’horloge locale ; la protection existante contre la manipulation de l’horloge s’applique toujours aux essais et aux licences à durée déterminée.
Bloc retiré ou modifiéLa signature ou la liaison échoue → le SDK rafraîchit ; hors ligne sans rien de valide → UnavailableOffline. Jamais « couvert ».
Bloc plus ancien rejouéNuméro de séquence inférieur → refusé ; la couverture reste au bloc le plus récent accepté.
Remboursement / révocation / transfertInchangé : révoquez la licence (l’application retombe en Free au rafraîchissement) ; les transferts conservent les conditions.
Votre plan n’inclut plus la couvertureAprès une période de grâce, le service émet des blocs non appliqués ; les builds appliquant les règles continuent de fonctionner ; les conditions sont préservées et les blocs appliqués reprennent dès la remontée de plan.

Cloud ou auto-hébergé

La couverture se comporte de façon identique dans les deux modes — les préréglages de politique, le bloc signé, les props MSBuild et les appels du SDK sont les mêmes. Ce qui diffère :

  • Droit d’accès. Sur Keyright Cloud, la couverture fait partie du plan supérieur (Scale et Enterprise) ; les plans inférieurs la voient verrouillée avec une invitation à monter de plan. En auto-hébergé, elle est incluse dans les licences qui portent la fonctionnalité de couverture. Dans les deux cas, un espace de travail qui perd ce droit reçoit des blocs non appliqués après une période de grâce plutôt que de verrouiller ses clients, et les blocs appliqués reprennent lorsque le droit revient.
  • URL de base / jeton. $BASE est https://keyright.delta1labs.com sur Cloud, ou l’URL de votre propre service d’émission en auto-hébergé ; $TOKEN est votre jeton de session du tableau de bord (Cloud) ou le jeton d’administration de tenant/d’environnement (auto-hébergé). Les ServiceUrl et PublicKeyBase64 du SDK pointent vers l’instance correspondante et sa clé de signature.
  • Tout le reste — l’API d’éligibilité (/v1/coverage), l’export/import hors ligne, le registre — est la même surface dans les deux modes.

Vérifier

  1. Donnez une politique à une édition (tableau de bord ou coveragePolicy sur l’upsert du tier), émettez une licence et ouvrez son onglet Coverage — confirmez que la phrase en langage clair et les conditions correspondent au préréglage.
  2. Lancez le vérificateur de couverture (ou GET /admin/licenses/{id}/coverage/check?version=…) pour une version que vous attendez couverte et une autre que vous attendez non couverte — confirmez le statut et la raison (WindowLapsed ou OutsideVersionRange).
  3. Compilez votre application avec KeyrightCoverageVersion + KeyrightCoverageEntitlementDate + KeyrightRequireCoverage=true, activez avec une licence couverte et confirmez que CheckCoverageAsync renvoie Covered ; passez la version du build au-delà des conditions et confirmez qu’il renvoie NotCovered avec la bonne action unique.
  4. Pour votre page de téléchargement, faites un POST /v1/coverage avec une clé client + une version et confirmez que eligible correspond à ce que voit l’application.
  5. Isolé (air-gapped) : téléchargez le bloc signé, importez-le avec ImportCoverageBlock sur une machine déconnectée et confirmez la même décision sans aucun accès réseau.

Voir aussi