Licensing
Everything in this repository is MIT licensed except the ee/ directory, which
is under the Antifailure Enterprise License. The community build does not
contain ee/ at all: it is a separate Go module the community build cannot
resolve, and CI has a job that fails if the community binary carries an
enterprise symbol.
Installing a license
Section titled “Installing a license”There is nothing to install. The enterprise binary reads its license from the environment and stores nothing, so a license is two variables set wherever the engine runs:
export AF_LICENSE_KEY=<token>export AF_ORG=globexaf license statusEvery enterprise setting is preserved when they are gone: features fall back to the community behaviour rather than failing.
So af license install and af license remove exist and both say so instead of
pretending. On the enterprise binary they name these variables; on the community
binary they refuse outright.
A license is an Ed25519 signed statement carrying the organisation it was issued to, the features it permits, the seat count, when it expires, and which key signed it. Verification is a signature check against keys stamped into the binary at release; it needs no network, which is what makes an air gapped installation possible.
An installation that mints its own licenses supplies its key in
AF_LICENSE_PUBLIC_KEYS, as kid=base64,kid=base64. Those are merged with the
build’s own rather than replacing them, taking precedence on a shared
identifier, because trusting your own key must not stop the vendor’s from
working.
When it does not verify
Section titled “When it does not verify”AF-EE-001 The enterprise license could not be verified. Next: Reinstall the license with 'af license install'; the token may have been truncated in transit.Almost always truncation.
Wrong organisation
Section titled “Wrong organisation”AF-EE-003 This license was issued for organization acme and this instance isglobex. Next: Install the license issued for globex.The organisation is inside the signature, so a license cannot be edited to name a different one. This is what stops a key being passed around.
AF-EE-002 The system clock is 3 days behind the last time this license wasseen. Next: Correct the system clock. Enterprise features resume once it passes the recorded time.Expiry is checked against the clock, and a clock that can be moved backwards is an expiry that can be avoided. The last seen time is recorded, so going backwards is detected rather than believed. Correcting the clock resolves it; nothing has to be reinstalled.
AF-EE-004 The license covers 25 seats and they are all in use. Next: Remove an inactive member, or ask for more seats at https://antifailure.dev/contact. No existing member was removed.The last sentence is the important one. Reaching a seat limit refuses the addition and never evicts somebody to make room.
Expiry and grace
Section titled “Expiry and grace”An expired license keeps working for a grace period, with a warning on every command.
After the grace period the enterprise features stop and everything else carries
on. The community edition is the whole product minus ee/, and an expired
license leaves you with it rather than with nothing.
What is in ee/
Section titled “What is in ee/”The features a license can name are air_gapped, audit_stream, billing, cloud_database, cloud_runtime, compliance_packs, enterprise_dashboard, enterprise_secrets, multi_runtime, policy_enforcement, rbac, scim, sso and support_access.
Of the 14 features a license can carry, 11 are refused when the license does not name them, 8 by the engine and 4 by the control plane, with some checked by both. The rest are listed here anyway, with what actually happens without each one, because a feature that is sold and never checked is worth knowing about and the number is only useful if it can come back unflattering.
The table is generated from ee/engine/feature/catalogue.go, the one place this
product records what a license permits. Every row saying a feature is refused
names the file that refuses it, and a test requires that file to carry the call
that refuses: feature.Enabled where the enterprise engine gates, or
edition.Permits where the community engine gates by name.
| Feature | What it is | Without it |
|---|---|---|
air_gapped |
An installation that reaches nothing outside the operator’s own network. | Withheld. airgapped/airgapped.go:RegisterFromEnvironment asks the license, and the feature is off when the answer is no. |
audit_stream |
Privileged actions forwarded to the organization’s own SIEM. | Withheld by both the engine at auditsink/auditsink.go:auditsink.permitted and the control plane at ee/web/server/src/register.ts:startAuditStream. Each checks the license. |
billing |
Subscriptions, invoices and the plan an organization is on. | Nothing changes, because the capability is not built yet. |
cloud_database |
Managed cloud database providers, the ones that need an organization behind them rather than a developer’s own card. | Withheld. cloudgate/cloudgate.go:gatedDatabase.Branch asks the license, and the feature is off when the answer is no. |
cloud_runtime |
Managed cloud runtime providers, on the same rule as the databases. | Withheld. cloudgate/cloudgate.go:gatedRuntime.Up asks the license, and the feature is off when the answer is no. |
compliance_packs |
SOC 2 and HIPAA evidence gathered from the control plane’s own records. | Withheld. compliance/command.go:Command asks the license, and the feature is off when the answer is no. |
enterprise_dashboard |
The console: environments, masking, egress, audit and workloads. | Nothing changes, because the capability is not built yet. |
enterprise_secrets |
Declared variables resolved from Vault or a cloud secret manager. | Withheld. secrets/source.go:Source.Available asks the license, and the feature is off when the answer is no. |
multi_runtime |
Placing an environment across several runtimes at once, by requirement and by tag. | Withheld. engine/internal/env/env.go:Orchestrator.placement asks the license, and the feature is off when the answer is no. |
policy_enforcement |
Organization policy that refuses an environment the manifest would have allowed. | Withheld. policyenforce/policyenforce.go:Hook.Check asks the license, and the feature is off when the answer is no. |
rbac |
Custom roles: a role an organization defines, granted to a member at a scope, on top of the four built-in roles. | Withheld by the control plane. ee/web/rbac/src/enforce.ts:customRoleResolver asks the license, and an unlicensed installation is answered 402 naming the feature rather than 404. |
scim |
Directory provisioning, so joiners and leavers arrive from the identity provider. | Withheld by the control plane. ee/web/scim/src/routes.ts:guard asks the license, and an unlicensed installation is answered 402 naming the feature rather than 404. |
sso |
Single sign on against the organization’s own identity provider. | Withheld by the control plane. ee/web/sso/src/store.ts:connectionByHandle asks the license, and an unlicensed installation is answered 402 naming the feature rather than 404. |
support_access |
A supported way for the vendor to see what a customer sees. | Nothing changes. It is implemented and deliberately available to everyone. |
A license carries fourteen names and the hosted plan gate carries one boolean. Both are real refusals and only the license one is keyed on what was bought.
Two of those cannot be sold
Section titled “Two of those cannot be sold”billing and enterprise_dashboard are names in the catalogue and nothing
else. There is no implementation of either, so there is nothing a license could
switch on, and both are refused twice: tools/licensegen will not sign a
request naming one, and the verifier carries the name through and never permits
it.
Custom roles were in a third state until 2026-09-11
Section titled “Custom roles were in a third state until 2026-09-11”rbac named something real that the license did not provide. The custom roles
library was complete and tested, and nothing stored a role model, so no
organization could have one. It was reported and not enforced, written down
rather than gated, because a check on a path nothing reaches is worse than no
check, and tools/licensegen printed a warning naming it beside every key it
signed.
That stopped being true on 2026-09-11. The enterprise control plane now stores a
role model per organization, mounts the routes that define one, and installs the
resolver every permission check asks, and that resolver asks the license and
then the organization’s entitlement before a stored grant widens anything. The
table above reads Withheld for rbac, and the site it names is the one that
asks. The warning is gone with the state it described. The four built-in roles
are unchanged and are not what the license sells: every organization on every
plan has them. Custom roles says how a model is
written, reviewed and applied.
air_gapped was in this state and said so nowhere until 2026-09-08. Every
occurrence of the name in the repository was a copy of the catalogue, the
license vectors, a line of documentation, or a test, so a license naming it
verified, reported itself active, printed in af license status, and granted
nothing. It is no longer in this state and this page said it was for a day
longer than it was true: the paragraph naming it stayed here while the gate that
made it false landed in another pull request, which is the same stale sentence
this catalogue exists to refuse and is why the row above is generated from the
code rather than written beside it. The table now reads Withheld for
air_gapped, and the site it names is the one that asks.
All three lists are held to the code by a test rather than by a habit.
notShipped in ee/engine/license/license.go is the single place the refused
set lives, and this page, the generator and the enterprise feature registry are
all checked against it in both directions. A fourth check asks the question none
of those could: that every feature a license can grant is either refused or
gated at a real site in one half of the product or the other. A third answer,
built and deliberately gated nowhere, was recorded in a map called unenforced
until custom roles, its last entry, were gated, and license.go says how to
bring it back with its checks if a feature is ever in that state again.
Contributing
Section titled “Contributing”Contributions are under the DCO, not a CLA. You keep your copyright. See
CONTRIBUTING.md.
Related: policy, runtimes, air gapped.