The problem
The product argument of Lease.ashx names one licensing product, and LeaseService.GetLicenseLeaseAsync uses it as a filter:
// A client that names no product is served from any pool, which is what every PostSharp
// client relies on: none of them sent the argument.
IReadOnlyList<string> productCodes = string.IsNullOrEmpty( productCode )
? []
: ProductCodes.Matching( productCode );
License[] licenses = await this.repository.Licenses
.Where( license => (productCodes.Count == 0 || productCodes.Contains( license.ProductCode ))
&& license.Priority >= 0 )
That gives a client two choices, and neither is right for Metalama.
Send nothing. The client is served from any pool. This is the legacy behaviour, and it means "any PostSharp license key" — it predates Metalama and no PostSharp client ever sent the argument. A Metalama client that sends nothing can be served a license key of a product that does not license Metalama at all; the client then refuses it (the license key is for X and not for Metalama) although the server held a key that would have worked, because the choice was made on the server, by Priority, without knowing who was asking.
Send one product. The client is served only from that pool. But a client does not know which edition its customer bought — only the server does. A client that sends MetalamaProfessional is refused by a server whose pool holds MetalamaEnterprise, and the customer sees No license with free capacity from a server that is holding exactly the licenses they paid for.
What a client actually needs to ask
A product family, not a product. A Metalama client needs to say "any license key that licenses Metalama", and today the protocol has no way to say it.
The family is not a guess — it is already declared, in ILicenseProductCatalog.IsProductOfFamily. For Metalama (MetalamaLicenseProductCatalog) it is:
MetalamaCommunity, MetalamaProfessional, MetalamaEnterprise, MetalamaUltimate, MetalamaStarter, MetalamaFree — and PostSharpUltimate and PostSharpFramework, because a PostSharp key of those editions licenses Metalama too.
So the set is neither "one product" nor "every product", and it is not derivable from the name by prefix.
Proposal
Let product name a family as well as a product: product=Metalama is served from every license whose ProductCode is a product of the Metalama family, and product=PostSharp likewise.
The server already references SharpCrafters.Backstage.Licensing and already has a catalog (LicenseServerProductCatalog : LicenseProductCatalog), so resolving a family name to its products is a lookup rather than a new concept. ProductCodes.Matching is the natural place: it already maps one name to the several spellings a database may hold, and this widens the same mapping from spellings to a family.
Compatibility is preserved in both directions: a client that names one product keeps being served from that pool alone, and a client that names nothing keeps being served from any, which is what the deployed PostSharp clients rely on.
Until then
The client side is ILicenseProductCatalog.LicenseServerProduct. It is today the evaluation product of the catalog, so a Metalama client sends product=MetalamaProfessional — the second of the two wrongs above, and the one that denies a customer whose pool is MetalamaEnterprise. That has to change to sending nothing before this ships: a customer whose server holds one pool is then always served, and a customer with a mixed server chooses with Priority. The hook stays available for a product that genuinely knows its own pool.
See postsharp-ops/SharpCrafters.Backstage#1 and metalama/Metalama#946 for the client side.
The problem
The
productargument ofLease.ashxnames one licensing product, andLeaseService.GetLicenseLeaseAsyncuses it as a filter:That gives a client two choices, and neither is right for Metalama.
Send nothing. The client is served from any pool. This is the legacy behaviour, and it means "any PostSharp license key" — it predates Metalama and no PostSharp client ever sent the argument. A Metalama client that sends nothing can be served a license key of a product that does not license Metalama at all; the client then refuses it (
the license key is for X and not for Metalama) although the server held a key that would have worked, because the choice was made on the server, byPriority, without knowing who was asking.Send one product. The client is served only from that pool. But a client does not know which edition its customer bought — only the server does. A client that sends
MetalamaProfessionalis refused by a server whose pool holdsMetalamaEnterprise, and the customer seesNo license with free capacityfrom a server that is holding exactly the licenses they paid for.What a client actually needs to ask
A product family, not a product. A Metalama client needs to say "any license key that licenses Metalama", and today the protocol has no way to say it.
The family is not a guess — it is already declared, in
ILicenseProductCatalog.IsProductOfFamily. For Metalama (MetalamaLicenseProductCatalog) it is:MetalamaCommunity,MetalamaProfessional,MetalamaEnterprise,MetalamaUltimate,MetalamaStarter,MetalamaFree— andPostSharpUltimateandPostSharpFramework, because a PostSharp key of those editions licenses Metalama too.So the set is neither "one product" nor "every product", and it is not derivable from the name by prefix.
Proposal
Let
productname a family as well as a product:product=Metalamais served from every license whoseProductCodeis a product of the Metalama family, andproduct=PostSharplikewise.The server already references
SharpCrafters.Backstage.Licensingand already has a catalog (LicenseServerProductCatalog : LicenseProductCatalog), so resolving a family name to its products is a lookup rather than a new concept.ProductCodes.Matchingis the natural place: it already maps one name to the several spellings a database may hold, and this widens the same mapping from spellings to a family.Compatibility is preserved in both directions: a client that names one product keeps being served from that pool alone, and a client that names nothing keeps being served from any, which is what the deployed PostSharp clients rely on.
Until then
The client side is
ILicenseProductCatalog.LicenseServerProduct. It is today the evaluation product of the catalog, so a Metalama client sendsproduct=MetalamaProfessional— the second of the two wrongs above, and the one that denies a customer whose pool isMetalamaEnterprise. That has to change to sending nothing before this ships: a customer whose server holds one pool is then always served, and a customer with a mixed server chooses withPriority. The hook stays available for a product that genuinely knows its own pool.See postsharp-ops/SharpCrafters.Backstage#1 and metalama/Metalama#946 for the client side.