Skip to content

Licensing model ​

Moʻolelo runs under the Business Source License v1.1 with a clear path to Apache 2.0 — every release reverts to Apache 2.0 four years after publication. The Vespa OpenFGA Searcher plugin is Apache 2.0 from day one, in a clearly isolated subdirectory.

In day-to-day operational terms, two things matter:

  1. Every deployment runs with a valid license key. There are no "no key required" build modes shipped to customers.
  2. Some capabilities are gated behind feature flags carried inside the license. The Community tier doesn't carry them; the Commercial tier does.

This page explains the model. Operational mechanics — installation, renewal, troubleshooting — live in License key management.

Tiers ​

There are two tiers customers see:

Community is free and works for any organization, any size. It covers single-tenant, single-region production use. You self-issue keys from your account at moolelo.dev — no contract, no manual review, no waiting list.

Commercial is paid and unlocks scale-out features (multi-tenant, multi-region) plus organization-level concerns (compliance support, signed releases, air-gap mirror pipeline, named TAM). Issuance is contract-driven; we'll talk to you about your environment before issuing a key.

The platform itself is identical across tiers. There are no Community-only feature gates that we artificially withhold from paying customers, and no Commercial-only capabilities we pretend Community users don't need. The license carries the difference; the binary doesn't.

Feature flags ​

The license payload carries a features array. Three flags are defined today:

  • multi_tenant — required to provision a tenant other than the well-known default sentinel. The webserver's tenant provisioner refuses second-tenant creation when this flag is locked.
  • multi_region — required when MOOLELO_VAULT_ENDPOINTS lists more than one region. Single-region deployments don't need this flag and can run on a Community license.
  • vault_substrate — required when any vault endpoints are configured (the privacy-substrate binding is enabled). Useful for tenants whose Compliance team wants explicit licensing of any cryptographic substrate, even at single-tenant single-region scale.

The flags are independent. A small operator running multiple tenants in a single region needs multi_tenant but not multi_region or vault_substrate. A single-tenant deployment that's spread across regions needs multi_region only. Each Commercial contract sets the flags appropriate to that customer's deployment shape.

When a feature gate trips, the binary refuses to start with a clear error pointing at which flag is missing. There is no silent fallback to the unlicensed mode — that would defeat the purpose of the gate.

Why time-bounded ​

Licenses are time-bounded by design — Community defaults to a one-year term, Commercial to whatever the contract specifies. This isn't a billing trick. It's the air-gap-correct revocation mechanism.

Moʻolelo's architecture commits to no internet connectivity at runtime in steady state. The platform never phones home, never pulls a CRL, never checks against a remote endpoint. So the natural revocation lever is to decline to issue your renewal. Your deployed envelope expires on its own; the platform's signature-verifying validator rejects post-expiry envelopes locally; the lights go out without anything reaching across the air-gap.

For air-gap operators specifically, the renewal flow looks the same as every other artifact transfer:

  1. ~30 days before expires_at, an operator with internet access pulls a renewed envelope from moolelo.dev/account.
  2. The envelope is transferred into the air-gap network through whatever secure channel handles image mirrors and ML weights.
  3. The renewed envelope is mounted and services restart to pick it up.
  4. The previous envelope stays valid until its own expires_at, so step 3 has a clean transfer window without an emergency.

Customers in non-regulated environments who want faster revocation — sub-renewal-cycle response to a compromised key — can opt into a CRL-pull path on the platform side. That feature is on the roadmap and is explicitly opt-in; air-gap deployments leave it disabled.

Signing and verification ​

Each Moʻolelo binary embeds the website's signing-root public key at build time. License envelopes are signed with the matching ed25519 private seed, which lives in a Cloudflare Workers Secret on the website side and never leaves it. The binary verifies the signature locally before parsing the claims; tampered envelopes are rejected at startup with license: invalid.

The website's keypair can be rotated. When it is, every existing customer needs a re-signed envelope, and platform binaries built before the rotation reject post-rotation envelopes (and vice versa). For that reason, key rotation is a deliberate per-major-release-generation event, not a routine operational action.

The signing material is never shared with customers in any deployment shape. Air-gap customers receive signed envelopes; they don't receive the signing key. There is no path to self-sign Moʻolelo licenses outside of forking the project and replacing the embedded public key in your fork — at which point you've voluntarily stepped outside the BSL grant.

Limit enforcement vs feature gates ​

Two distinct enforcement shapes live in pkg/license:

Limits carry caps for max_employees, max_events_per_day, max_regions, and max_tenants. Their enforcement depends on the dimension:

  • Seats (max_employees) are counted as active subjects: distinct identities that hit the query coordinator inside a rolling MAU (monthly active users) window, default 30 days. The signed claim's mau_window_days field controls the window. The signed claim's seat_enforcement field — "hard" (default) or "soft" — picks behaviour past the cap. Hard refuses new subjects with ErrSeatLimitExceeded (gRPC ResourceExhausted / HTTP 429 over MCP). Soft admits new subjects but emits an audit row and surfaces an admin-UI banner — useful for pilots and Community trials where the platform shouldn't be hostile. Existing subjects already inside the window are never disrupted regardless of mode.
  • Events per day, regions, tenants stay soft-warn for now; Commercial contracts specify what a sustained over-limit triggers (typically a true-up bill, not a service interruption).

Feature gates are hard. The binary refuses the operation when the flag is locked. There's no soft-warn-and-continue equivalent — multi_tenant either unlocks tenant N+1 or it doesn't.

The seat counter is authoritative on the master webserver (the control plane). Regional query coordinators hash subjects locally with the deployment-wide HMAC key — only the hash crosses to the master, so personal data never leaves region. Every new-subject decision goes through POST /api/internal/seat-claim on the master, which owns the single cluster-wide active_subjects ledger and runs the cap check atomically. A 1-hour in-process cache on each coordinator keeps repeated hits from the same subject from round-tripping per request. If the master is unreachable, the coordinator denies new claims (fail-closed) — a control-plane outage cannot be used to overshoot the cap.

Operators tune two operator-facing knobs from the admin UI:

  • Seat exclusions — comma-separated glob patterns (e.g. svc-*,bot-*,*@bots.example.com) matched against the raw subject before hashing. Matching subjects skip the cap entirely. Service accounts, automation, and the admin UI itself typically belong here.
  • Per-tenant seat caps — admins can carve the cluster cap into per-tenant allocations from the Tenants page. Missing rows mean "no per-tenant cap; only the cluster cap applies".

The distinction matters operationally. A Community tenant who briefly exceeds the events/day soft cap sees a log warning and keeps running. A Community tenant whose MAU crosses the seat cap with seat_enforcement: "soft" keeps admitting users with a banner; with "hard", the next new user gets a 429. A tenant who tries to provision a second tenant under a multi_tenant-locked license gets ErrLicenseFeatureLocked and the call refuses. The first is a billing conversation; the second is an architectural one.

What's open and what isn't ​

The Moʻolelo source code is source-available under BSL. You can read it, run it, modify it for your in-house use, and audit any line of it. What you can't do during the BSL period is redistribute it as a competing managed service. Every release reverts to Apache 2.0 four years after publication, so today's code becomes fully open source on a known schedule.

The Vespa OpenFGA Searcher plugin under deploy/vespa/components/openfga-searcher/ is Apache 2.0 from day one, marked with SPDX headers, and CI-gated to enforce its license isolation from the rest of the BSL repo. It's designed to be extractable into its own project later.

If your use case doesn't fit this licensing model, tell us — we'd rather hear it than have you fork and fight the license.

Moʻolelo — self-hosted federated enterprise knowledge platform.