# Kanject.Core.Features

One source tree, several product artifacts — Free, Pro, Enterprise, a customer build, an offline variant. Declare the lock once, on the capability, and Features wires the edition constants, the service registrations, the runtime gates, and the publish checks. **Edition locks decide what ships. Runtime flags decide what runs.** The runtime and generator are free under the Community licence.

## Install

```bash
dotnet add package Kanject.Core.Features
dotnet add package Kanject.Core.Features.Annotations
```

```xml
<ItemGroup>
  <PackageReference Include="Kanject.Core.Features" />
  <PackageReference Include="Kanject.Core.Features.Annotations" PrivateAssets="all" />
</ItemGroup>
```

> **Delete the `IncludeAssets` line:** `dotnet add package` writes an `IncludeAssets` line for the Annotations package that omits `compile`. Delete that line and keep `PrivateAssets="all"`, or the attribute types can be stripped from compilation.

## Define the ladder, lock a service

Mark one enum as the edition ladder, then put `[LockedBehind]` on anything that should only exist from a given edition upward:

```csharp
using Kanject.Core.Features.Annotations.Attributes;

// Declaration order is unlock order: Pro includes everything in Free.
[EditionLadder]
public enum Edition
{
    Free,
    Pro,
    Enterprise,
}

// A whole capability that only ships from Pro upward.
[LockedBehind(Edition.Pro)]
public sealed class AdvancedExportEngine : IExportEngine
{
    public ExportResult Export(Project project) => /* premium implementation */;
}
```

```csharp
// Generated. Registers the services this edition unlocks; the rest are omitted.
services.AddLockedFeatures();
```

In a Pro build, `AdvancedExportEngine` is registered as itself and as `IExportEngine`. In a Free build those registration lines are not emitted at all.

## Publish an edition

```bash
dotnet publish -p:KanjectEdition=Free -p:PublishTrimmed=true -o ./dist/free
dotnet publish -p:KanjectEdition=Pro  -p:PublishTrimmed=true -o ./dist/pro
```

The edition is a build-time fact. When `KanjectEdition` is not set the lowest edition is selected, so the safe default never unlocks anything by accident. Branch on the generated constants for edition-aware code that is not a service:

```csharp
// Generated beside the enum. A false branch is dead code the trimmer removes.
if (Editions.HasPro)
{
    menu.Add(new AdvancedExportCommand());
}
```

## Gate at runtime

For code that ships in every artifact but should not always run — a beta, a gradual rollout, a kill switch — lock a `partial` method behind a string flag. The generated guard lives in the method body, so every route into the method passes through it:

```csharp
using Kanject.Core.Features.Annotations.Attributes;
using Kanject.Core.Features.Interfaces;

public sealed partial class Dashboard
{
    private readonly IFeatureGate _gate;

    public Dashboard(IFeatureGate gate) => _gate = gate;

    // A string means "decide at runtime". The real logic lives in {Method}Core.
    [LockedBehind("beta.export", Fallback = nameof(Locked))]
    public partial string Render();

    private string RenderCore() => "high-res export";
    private string Locked() => "Upgrade to export.";
}
```

Then register a gate. Each helper registers a singleton `IFeatureGate`, and the first registration wins:

```csharp
using Kanject.Core.Features.Extensions;

services.AddInMemoryFeatureGate("beta.export");
// or: services.AddFeatureGate(key => myRemoteFlags.IsOn(key.Name));
// or: services.AddFeatureGate<MyEntitlementGate>();
```

With the feature off and no `Fallback` declared, the guard throws `FeatureLockedException`.

## Which lock to reach for

- **A whole capability with its own service** — `[LockedBehind(Edition.Pro)]` on the class, then `AddLockedFeatures()`
- **A small premium contribution inside shared code** — an edition-locked `partial void` method the compiler elides in lower editions
- **The strongest boundary** — move the code to its own project and include it with an edition-conditional `<LockedReference>`
- **Shipped code that should not always run** — `[LockedBehind("flag")]` on a `partial` method, backed by `IFeatureGate`
- **Present in every edition but gated** — `[LockedBehind(Edition.Pro, Binding = FeatureBinding.Runtime)]`, for an in-app upsell
- **Proof that code is absent** — `[PrunedBelow(…)]` with `<VerifyEditionPruning>` checking the published artifact

> **Distribution hygiene, not DRM:** Compile-time locking protects code you did not ship. A runtime check inside a shipped binary can still be bypassed by whoever owns the machine. When code must be absent from a lower edition, publish that edition trimmed or with Native AOT and verify it; for high-value operations, enforce authorization on the server.

## What ships with it

- `[EditionLadder]` and the `KanjectEdition` build property — one ladder, one artifact per edition
- Generated `Editions.Has…` compile-time constants and `AddLockedFeatures()` service registration
- `[LockedBehind]` on classes (edition) and `partial` methods (edition or runtime flag), with an optional `Fallback`
- `IFeatureGate` with in-memory and delegate gates — `AddInMemoryFeatureGate`, `AddFeatureGate`
- `<LockedReference>` for edition-conditional project and package references
- `[PrunedBelow]` and post-publish pruning verification — absence as a CI-checkable property
- Compile-time `KANFL` diagnostics, including a check for lower-edition code that injects a higher-edition service

> **Pairs with Packages:** [Packages](https://www.kanject.com/docs/core-packages/) decides which provider handles a port; Features decides whether that provider ships or runs. To keep a provider out of a lower edition, put it in its own assembly behind a `<LockedReference>` and validate the package graph per edition.

> **Free runtime, licensed entitlements:** `Kanject.Core.Features` and its generator are published on public NuGet and free under the Community licence — individuals, open source, education, and companies under $250K. Signed entitlement tokens (`Kanject.Core.Features.Entitlements`) and the edition-aware test kit (`Kanject.Core.Features.TestKit`) ship from the private feed with a commercial Core licence.

---
_Source: https://www.kanject.com/docs/core-features/ · Kanject Docs_
