License Capability Model
Muonroi implements a three-tier license capability system that controls runtime feature access. Every application starts in Free tier and can be upgraded to Licensed or Enterprise based on activation.
Overview
The capability model enforces feature gates at runtime using:
- LicenseState — carries tier, validity, and optional activation metadata
- ILicenseGuard — provides feature checking and validation APIs
- Feature strings — stable identifiers like
"rule-engine","multi-tenant", etc.
The system is designed to be fail-closed: if a feature check fails at startup, registration fails fast rather than silently degrading.
The Three Tiers
Free Tier
The default tier for all applications. Includes only core database and HTTP operations—no license key required.
Available features:
db.query— Database read operationsdb.save— Persist changes to databasedb.add— Add new recordsdb.update— Modify existing recordsdb.delete— Remove recordshttp.request— Make outbound HTTP callsapi.validate— Basic API validation
Use case: Prototype, hobby projects, community development.
Licensed Tier
Unlocked with a valid license key. Adds developer productivity tooling and control plane integration.
Includes all Free features, plus:
vsix.publish— Publish VSIX packages from Visual Studiovsix.watch— Watch VSIX files for live reloadvsix.explorer— Code explorer in VSIX extensioncp.publish— Publish rules to Control Plane
Use case: Teams building production applications with integrated development workflows.
Enterprise Tier
Unlocked with an Enterprise license. Includes the full feature set with governance, multi-tenancy, and advanced security.
Includes all Licensed features, plus:
rule-engine— Execute dynamic business rules at runtimemulti-tenant— Isolate data and configuration per tenantadvanced-auth— Custom authorization policies (OpenFGA, OPA)audit-trail— Immutable audit log with HMAC chain verificationanti-tampering— Startup code integrity checks and runtime guardsgrpc— gRPC service communicationmessage-bus— Event distribution via message brokerdistributed-cache— Shared cache (Redis) across instancesconnectors— External system connectors (Stripe, Salesforce, etc.)js-expressions— JavaScript expression evaluation in rules
Use case: Large organizations requiring multi-tenant SaaS platforms, regulatory compliance, and real-time rule execution.
Complete Feature Matrix
| Feature | Free | Licensed | Enterprise |
|---|---|---|---|
| Database Operations | |||
| db.query | ✓ | ✓ | ✓ |
| db.save | ✓ | ✓ | ✓ |
| db.add | ✓ | ✓ | ✓ |
| db.update | ✓ | ✓ | ✓ |
| db.delete | ✓ | ✓ | ✓ |
| HTTP & API | |||
| http.request | ✓ | ✓ | ✓ |
| api.validate | ✓ | ✓ | ✓ |
| Developer Tools | |||
| vsix.publish | ✓ | ✓ | |
| vsix.watch | ✓ | ✓ | |
| vsix.explorer | ✓ | ✓ | |
| cp.publish | ✓ | ✓ | |
| Business Logic | |||
| rule-engine | ✓ | ||
| multi-tenant | ✓ | ||
| advanced-auth | ✓ | ||
| audit-trail | ✓ | ||
| anti-tampering | ✓ | ||
| Infrastructure | |||
| grpc | ✓ | ||
| message-bus | ✓ | ||
| distributed-cache | ✓ | ||
| Integrations | |||
| connectors | ✓ | ||
| js-expressions | ✓ |
API Reference
ILicenseGuard Interface
The main service for runtime feature checking. Inject ILicenseGuard into any .NET service.
public interface ILicenseGuard
{
/// Gets the current license state (tier, validity, payload)
LicenseState Current { get; }
/// Gets the effective tier: Free, Licensed, or Enterprise
LicenseTier Tier { get; }
/// True if running in Free tier
bool IsFreeMode { get; }
/// Validates a feature is available. Throws if not allowed.
void EnsureValid(string actionType, string? actionName = null,
string? payloadHash = null, string? correlationId = null);
/// Checks if a feature is available. Returns bool (no throw).
bool HasFeature(string featureName);
/// Ensures a feature is licensed. Throws if unavailable.
void EnsureFeature(string featureName);
/// Records an action in the HMAC chain (for audit trail).
void RecordAction(LicenseActionContext context);
/// Gets the current rolling token for chain verification.
string GetChainToken();
/// Decrypts data using license-derived keys.
string DecryptSecurely(string purpose, string encryptedData,
Func<string, string, string> decryptor);
}
LicenseState Model
Represents the complete license state at runtime.
public sealed class LicenseState
{
/// License is valid (not expired, not revoked)
public bool IsValid { get; init; }
/// License has exceeded its expiry date
public bool IsExpired { get; init; }
/// Error message if validation failed
public string? Error { get; init; }
/// Parsed license payload (key, tier, features)
public LicensePayload? Payload { get; init; }
/// Server-signed activation proof (tier, heartbeat nonce, expiry)
public ActivationProof? ActivationProof { get; init; }
/// Effective tier: Free, Licensed, or Enterprise
public LicenseTier Tier { get; init; }
/// License key identifier (from activation proof)
public string? LicenseId { get; init; }
/// Organization name (from activation proof)
public string? OrganizationName { get; init; }
/// Expiry date (from activation proof)
public DateTimeOffset? ExpiresAt { get; init; }
/// List of enabled feature strings
public string[]? Features { get; init; }
/// Checks if a specific feature is allowed
public bool HasFeature(string featureName)
=> LicenseCapabilityResolver.HasAccess(this, featureName);
/// Creates a Free tier license (always valid)
public static LicenseState CreateFree() { ... }
}
Usage Examples
Check a Feature at Runtime
Use HasFeature when you want to gracefully degrade:
public class OrderService(ILicenseGuard license)
{
public void ProcessOrder(Order order)
{
if (license.HasFeature("rule-engine"))
{
// Apply advanced business rules
var result = await _ruleEngine.ExecuteAsync(order);
}
else
{
// Fall back to basic processing
ApplyBasicValidation(order);
}
}
}
Require a Feature
Use EnsureFeature when a feature is mandatory:
public class AuditService(ILicenseGuard license)
{
public void LogAction(string action, object data)
{
license.EnsureFeature("audit-trail"); // Throws if not Enterprise
_auditStore.Insert(new AuditEntry { Action = action, Data = data });
}
}
Check Tier Directly
Access the tier for conditional logic:
public class ConfigService(ILicenseGuard license)
{
public void ConfigureTenancy()
{
if (license.Tier == LicenseTier.Enterprise)
{
services.AddMultiTenancy(); // Full isolation
}
else
{
services.AddBasicTenancy(); // Shared resources
}
}
}
Startup-Time Fail-Fast
Ensure a feature exists before registration completes:
// Program.cs
services.AddLicenseProtection(configuration);
services.AddRuleEngine(); // Requires "rule-engine" feature
// If license doesn't include "rule-engine", throws at startup
services.EnsureFeatureOrThrow("rule-engine");
Runtime Enforcement
Activation Flow
-
On Startup:
LicenseActivatorreads license key from file or environment- POST
/activateto License Server with machine fingerprint - Receive signed
ActivationProofcontaining tier, nonce, heartbeat config - Save proof to
licenses/activation_proof.json
-
At Runtime:
EnsureValidchecks: is license expired? Is it revoked?- Grace period applies if heartbeat fails (24 hours default)
HasFeatureresolves capability viaLicenseCapabilityResolver- If Enterprise: HMAC chain verification, anti-tampering checks
-
Grace Period:
- If heartbeat cannot reach License Server, degrade to Free tier
- Give 24 hours for operator to fix connectivity
- After 24h, license becomes invalid and fails hard
Feature Resolution Strategy
HasFeature uses this logic:
- If tier is Enterprise, all features allowed (payload:
["*"]) - If tier is Licensed, check explicit
Featureslist in payload - If tier is Free, only allow features in
FreeTierFeatures.All - Backward compatibility: legacy feature aliases map to modern names
Example:
// Free tier — only core database/HTTP
guard.HasFeature("db.query") // true
guard.HasFeature("rule-engine") // false
// Licensed tier — depends on payload
guard.HasFeature("cp.publish") // depends on Features array
// Enterprise tier — everything
guard.HasFeature("anything") // true
Configuration
appsettings.json
{
"LicenseConfigs": {
"Mode": "Online",
"LicenseFilePath": "licenses/license.key",
"ActivationProofPath": "licenses/activation_proof.json",
"FallbackToOnlineActivation": true,
"EnableChain": true,
"FailMode": "Hard",
"Online": {
"Endpoint": "https://license.truyentm.xyz",
"EnableHeartbeat": true,
"HeartbeatIntervalMinutes": 240,
"RevocationGraceHours": 24,
"TimeoutSeconds": 10
}
}
}
Program.cs Registration
services.AddLicenseProtection(configuration); // Base license checks
services.AddMEnterpriseGovernance(configuration); // Anti-tamper + HMAC chain
Best Practices
1. Prefer HasFeature Over Tier Checks
Good:
if (guard.HasFeature("audit-trail")) { ... }
Avoid:
if (guard.Tier == LicenseTier.Enterprise) { ... } // Too specific
2. Fail Fast at Startup
If a feature is mandatory, validate at registration time:
services.EnsureFeatureOrThrow("rule-engine");
3. Graceful Degradation
For optional features, use HasFeature to offer fallback behavior:
if (license.HasFeature("multi-tenant"))
await _tenantsService.IsolateAsync();
else
await _defaultTenantService.PrepareAsync();
4. Log License Events
On activation or expiry:
logger.LogInformation("License activated: {Tier} for {Organization}",
license.Current.Tier, license.Current.OrganizationName);
5. Honor the Grace Period
Don't immediately fail on heartbeat timeout. Wait for the full grace period:
// Heartbeat fails
// Wait 24h before degrading to Free
// Operator has time to restore connectivity
Cross-References
- License Activation — How to activate and renew licenses
- Tier Enforcement — Runtime validation and grace period handling
- License Server API — Generate keys, manage revocations
- Multi-Tenancy with Enterprise — Requires Enterprise tier
- Rule Engine Setup — Requires
rule-enginefeature