Monetization model + technical license enforcement
Esta página aún no está disponible en tu idioma.
Status note (2026-07-13): the canonical GitHub org is now Artist-Alley-Org (v0.1.0 org move, 2026-07-11) — repo links in this ADR have been host-swapped. The pre-fold migration links in the References section now resolve to the folded v0.1.0 baseline (
app/internal/db/migrations/00001_baseline_v0_1.sql, per ADR 0057).
Context
Section titled “Context”ADR 0016 establishes the legal license direction (AGPL + commercial
dual-license). This ADR specifies the runtime monetization model: the
tier shape, the .lic file format, the enforcement architecture, and
what we explicitly are not doing.
The model uses Ed25519-signed .lic files with tier-based feature
flags and optional host binding — a well-trodden pattern for
self-hostable commercial software — with tier limits sized for the
studio audience and a “same features at every tier” stance to
preserve the open-source community story.
Decision
Section titled “Decision”Three tiers. Same features at Community and Pro — the upgrade is purely scale. Enterprise adds procurement-friendly differentiators.
| Tier | Active seats | Asset cap | Differentiators |
|---|---|---|---|
| Community | 15 | 50,000 | Full feature set. Free. |
| Pro | 50 | 500,000 | Full feature set. Paid. |
| Enterprise | unlimited | unlimited | + audit log export, multi-tenant, HA / clustering, priority support |
Amended 2026-07-20 (ADR 0066): generic SAML / OIDC / LDAP SSO moved out of Enterprise to the free tier — auth is security hygiene, not a paywall. The managed hosted-IdP bridges + SCIM (
aa-sso-premium, ADR 0038) remain paid. Federation was likewise never gated (ADR 0041); it was listed here in error and has been dropped from the row.
Active seats are defined as users with last_active_at within the last 30 days, not registered users. Studios cycle contractors heavily;
this definition is honest about real usage and removes the upgrade-cliff
caused by registered-seat counts inflating from short-lived contributors.
Asset is defined as a single uploaded file. Re-uploading the same
content increments the counter. Storage-layer deduplication (content
hashing) is an independent optimization — it saves bytes without
affecting the license count. The two concerns are orthogonal.
License file format
Section titled “License file format”Ed25519-signed JSON, .lic extension. Claims:
{ "version": 1, "kid": "2026-01-key1", // public key id, for rotation "claims": { "tier": "pro", // community | pro | enterprise | dev "seats": 50, // null = unlimited "asset_cap": 500000, // null = unlimited "features": [], // explicit feature flags (rare; tier covers most) "owner_email": "ops@studio.example", "aud": "studio-name-slug", "nbf": 1735689600, // not-before (Unix epoch) "exp": 1767225600, // expiry (Unix epoch) "host_fingerprint": null, // optional host binding (Enterprise air-gap) "trial": false, // Enterprise 60-day trial flag "version": 1 }, "signature": "base64(ed25519)"}Notes on the format:
- All fields are designed in now even where unused at launch. Format stability matters because issued licenses must verify for years.
features[]exists for special bundles (e.g., a Pro customer who pre-paid for Enterprise SSO via a partner deal). At launch we expect this list to be empty for nearly all licenses.host_fingerprintis optional and only used for air-gapped Enterprise customers. Standard Pro and Community licenses are not host-bound.trialgates Enterprise 60-day trial licenses minted after a sales conversation. No Community-or-Pro trial exists (see “Not doing” below).
Trial mechanism
Section titled “Trial mechanism”- No Community trial. Community is free permanently.
- No Pro trial. Pro and Community share the full feature set, so a trial would only offer “slightly more seats” — not a meaningful evaluation experience. Upgrades happen when Community caps are hit.
- Enterprise: 60-day trial. Minted after a sales conversation. Same
.licfile format withtrial: trueand a 60-dayexp. Reverts to Pro behaviour on expiry (the binary degrades to Pro caps, not to unlicensed state, so the studio is not locked out of their own data).
Enforcement architecture
Section titled “Enforcement architecture”The verification logic is in plain Go source in the open-source distribution. No binary blobs, no private-repo dependencies — both would break studio source-audit requirements at AAA procurement. The realistic deterrent against casual license stripping is layered:
-
Tangled value derivation across many consumers. No single
if !license.valid { reject }gate. Instead, license state is the input to multiple boring-looking derived values:searchQuota— daily search ops budgetuploadConcurrency— parallel-upload worker countassetCap— used by upload service to refuse over-quota uploadsenabledPlugins— set of registered plugin handlersfederationEnabled— bool consumed by the federation subsystemcacheSize— cache.Registry sizingthumbnailMaxRes— preview pipeline output bound
Stubbing the verifier to “always return valid” produces a “valid” license whose derived values are all zero — breaking the app’s surface behaviour in confusing ways. Restoring it requires chasing every consumer, which is friction casuals will not invest in.
-
Multiple check points across the request lifecycle. Verification is invoked on app startup, on upload acceptance, on admin route entry, on a background heartbeat (every 15 minutes), and on selected paid feature use (e.g., enabling multi-tenant in admin). Removing one trigger leaves others firing.
-
Legal / commercial license terms (the actual deterrent). The commercial license explicitly forbids removing or bypassing the license check for commercial deployment. This is what makes procurement at any large studio say no — the compliance and audit risk, not the technical difficulty.
Not doing
Section titled “Not doing”- No binary obfuscation (e.g., garble). Obfuscation only mangles the binary; the source on GitHub is the manual for stripping. Spending effort on it would be feel-good security theatre.
- No closed-source license module distributed as a binary blob. Breaks studio source-audit requirements at AAA procurement — the same buyers we most want would reject the product outright.
- No online phone-home / revocation check for Community and Pro. Studios with security audits require zero outbound traffic from artist-alley nodes; phone-home would kill the air-gap story. Revocation is handled by short license terms (1 year) — non-renewing customers simply have their license expire.
- No cloud-gated paid features at launch (e.g., centrally rate-limited AI gateway). Possibly later, but the cost of running it for the Community tier eats margin and the model works without it. Revisit if hostile forks materialize.
Signing infrastructure
Section titled “Signing infrastructure”- Private Ed25519 signing key lives in Cloudflare Workers Secrets, never in the repo or on a customer’s machine. Worker exposes a minimal admin-authed endpoint to mint signed licenses.
- Cloudflare D1 (or KV) stores the issued-license database for the back-office: customer email, org slug, tier, issuance date, renewal cadence.
- Public verification keys are baked into the Go binary, one
per
kid. Adding a new key is a code change; rotating the active signing key requires shipping a release that knows the newkid. - License purchase / management portal runs on Cloudflare Pages, bound to the Workers backend.
Implementation phasing
Section titled “Implementation phasing”The work is gated on Phase 1.17 (Identity & teams) because seat counting
needs last_active_at per user.
- Phase 1.24.A — license file format + Ed25519 verification + public key bake-in + admin license-status surface.
- Phase 1.24.B — tangled value derivation across consumers (search quota, upload concurrency, cache sizing, plugin gating, federation gating).
- Phase 1.24.C — Cloudflare Worker signing service + license database + admin minting flow.
- Phase 1.24.D — Customer portal on Cloudflare Pages.
Status (2026-06-04)
Section titled “Status (2026-06-04)”The initial verification + admin surface from this ADR shipped earlier
than planned — folded into Phase 1.17 as the O / P-foundation
series on feat/identity-teams, since enterprise SSO providers needed
the gate baked in from the start. What’s live in main once the
branch lands:
- 1.17.O-1 — Go verifier package + embedded publisher key catalog
GET /admin/license/status+ admin status UI. Replaces the “1.24.A admin status surface” deliverable.
- 1.17.O-2 —
POST /admin/license/{validate,upload}+ thecapabilities.required_license_featurecolumn + theauth.Identity.Canlicense-bridge that runs before the SuperAdmin shortcut. Replaces the “1.24.A verification” deliverable and starts the tangled-derivation work (1.24.B) via the capability-system seam. - 1.17.O-3 — Layer-1 cross-binding: customer-held
org.keyseed, verified against theorg_pubkeyclaim at boot, on every upload, and on the 24h re-verify ticker. Defeats license sharing without any phone-home. - 1.17.P-foundation —
auth.IdentityProviderinterface + registry. Enterprise providers (ldapauth,samlauth) refuse registration without their feature flag and detach hot when the license downgrades; the multi-tenant manager (tenancy.Manager) follows the same construction-site gate pattern. Federation is explicitly not gated (per user direction — small communities self-organize freely).
The Cloudflare side (1.24.C/D — signing service, customer portal)
lives in the sibling artist-alley-license-server repo and is
deployed; the artist-alley app embeds only the public verification
catalog. The remaining work captured by this ADR is the broader
tangled-value derivation across more consumers (search quota, upload
concurrency, etc.) — still queued as Phase 1.24.B.
Public references for the as-built shape:
app/internal/licensing/— verifier + state + admin handler.app/internal/auth/identity_provider.go— provider registry + license-gatedRegister/Replace.app/internal/auth/license_bridge.go— install-level capability gate consumed byIdentity.Can().app/internal/db/migrations/00001_baseline_v0_1.sql— the feature-flag column + Enterprise capability seed rows, folded into the v0.1.0 baseline per ADR 0057.
Consequences
Section titled “Consequences”Positive
- Tier model is generous enough that Community is a usable on-ramp, not a crippled demo — preserves the “OSS community” pitch.
- “Same features, different scale” is the cleanest upgrade conversation in the OSS-with-paid-tier space (“you grew” beats “we put X behind a paywall”).
- Active-seats definition is honest about contractor churn and removes the upgrade cliff.
- Format-stable license file means issuance changes never require shipping a new binary.
- Signing key on Cloudflare Workers is a small, well-defined attack surface to defend.
Negative
- The license counter and active-seats query is non-trivial code we now own — bugs here block legitimate customers. Mitigation: extensive tests, fail-open on derivation errors so a verifier bug doesn’t lock a paying customer out of their own data.
- Tangled derivation makes the code slightly harder to navigate for
maintainers. Mitigation:
license.godocuments the consumer set comprehensively, derivation functions are clearly named. - We eat AI compute costs for Community when AI auto-tagging ships. Mitigation: bound Community AI budget per tier in the tangled derivation; revisit cloud-gated AI if costs exceed budget.
Alternatives considered
Section titled “Alternatives considered”- Adne’s exact tier sizes (3 seats / 100k assets Community, 10 / 1M Pro). Rejected — too restrictive for an OSS community-funnel strategy. artist-alley wants a larger free tier to attract contributors and adoption.
- Feature-gated tiers (Community without annotations, Pro with). Rejected — friction over “which features did I lose?” beats the cleaner “you grew” upgrade conversation. Same-features-different-scale is a stronger pitch for OSS.
- Cloud-gated AI from day one. Deferred. We accept the Community AI compute cost short-term in exchange for the cleaner “self-host everything” story.
- Garble + obfuscated license module. Rejected per “Not doing”.
Reference
Section titled “Reference”- ADR 0016 — license direction (AGPL + commercial dual-license).
- ADR 0018 (planned) — Blender as optional worker container.
- Phase 1.24 in
docs/roadmap.md— implementation sequence.