Skip to content

chore(license): relicense the core under Apache 2.0, isolate paid code under commercial/ - #128

Merged
btouchard merged 12 commits into
mainfrom
037-apache-relicense
Sep 29, 2026
Merged

btouchard merged 12 commits into
mainfrom
037-apache-relicense

Conversation

@btouchard

Copy link
Copy Markdown
Contributor

Moves the core from AGPL-3.0 to Apache 2.0 and isolates the code that only runs with a Personal or Pro key.

What changes

  • One source for editions: the tier table (capabilities, quotas, history windows) and the licence reader are the single place that decides what each edition opens. Every quota now reads from it.
  • Paid code under internal/commercial/ and frontend/src/commercial/: CVE/changelog/risk enrichment, posture scoring, email/Telegram/Slack/Teams channels, status page incidents/subscribers/personalization, escalation, maintenance suppression, the gRPC agent server, and their UI.
  • Import direction: no core package imports commercial; only cmd/maintenant registers it. A boundary test enforces this.
  • Downgrade behaviour: paid channels stop sending when the running edition no longer opens them, and a critical banner lists the suspended channels.
  • Licences: LICENSE is Apache 2.0, commercial/ directories carry the Maintenant Commercial Source License (readable, open to contributions, production use tied to a matching key). NOTICE added, COMMERCIAL-LICENSE.md rewritten as customer terms, CLA dropped. Source headers are two-line SPDX headers.

Still one build, one binary: the commercial package is always compiled. API responses and refusals (403 EDITION_REQUIRED) are unchanged, except the additive tiers and suspended_channels fields on /api/v1/edition.

The licence manager moves to internal/commercial/license and is reached
through extension.EditionSource. The tier table (capability to edition,
caps and history per edition) lives in internal/commercial/tiers.go on top
of the Community policy declared by the core. cmd/maintenant registers it
before building the app; a boundary test keeps core packages from
importing internal/commercial.

GET /api/v1/edition and get_edition gain an additive tiers field, which
the Editions page now reads instead of its own copy of the caps. Dead
extension scaffolding and an always-true swarm capability check go away.
The endpoint, heartbeat and certificate services default to
extension.Limit instead of their own literals, the refusal names the
edition that lifts the cap through extension.LiftingEdition, and the
upsell copy reads host counts from the edition tiers. A handler-level
test covers every capped resource on Community, Personal and Pro.
…mercial package

The enrichment pipeline moves to internal/commercial/updates and is
handed to the app through internal/extpoint, filled by
commercial.Extensions() in cmd/maintenant. The factory still decides at
startup from the cve_enrichment capability. The tier table moves to the
leaf package internal/commercial/tiers so core tests can register it
without importing the rest of the commercial code.
The scorer moves to internal/commercial/posture behind the core
security.PostureScorer interface and reaches the app through extpoint.
Readers, acknowledgments and response types stay in internal/security so
the store keeps importing only the core. The scorer is still built in
every edition and its routes stay behind the security_posture wall.
…ckage

The notifier delivers through a registry of ChannelSender keyed by
channel type; webhook and discord are core, email, telegram, slack and
teams move to internal/commercial/channels and are registered through
extpoint. Destination and credential validation for a channel type goes
through the same registry. An unregistered type still falls back to the
generic webhook.
After a licence downgrade, email, telegram, slack and teams channels
above the running edition stop delivering: alerts, escalation sends and
test sends record a suspended delivery naming the edition required. The
channel API and GET /api/v1/edition expose the suspension, and the UI
shows a critical banner on every page plus a badge on each suspended
channel.
…ckage

The status page SMTP client, maintenance scheduler, personalization
management, automatic incidents from alerts and subscriber notifications
move to internal/commercial/statuspage behind core contracts in
status/extension.go, reached through extpoint and built in every edition
as before. The public page, its reader and the stores stay in the core.
…ommercial package

The escalation service, runner, overlap detection and retention move to
internal/commercial/escalation behind the core escalation.Service
interface; internal/alert/escalation keeps the models and the Store
contract used by internal/store. The maintenance suppressor moves to
internal/commercial/maintenance. Both are reached through extpoint and
keep their startup decisions: runner and suppressor only when the
edition opens them, service always built.
…kage

internal/agentserver becomes internal/commercial/multihost and reaches
the app through extpoint.MultiHost. What the core agent client and the
REST handlers share with it (log capability, command errors, spool
state, public URL resolution) moves to the core package
internal/agentproto. The server mode gate, the enrollment cap and the
embedded agent keep their current conditions. CI runs the PostgreSQL
suite on ./internal/commercial/... in place of the old path.
Escalation, posture, status page incidents, maintenance, SMTP,
subscribers and personalization, agent enrollment and the update
CVE/changelog/risk panels move to frontend/src/commercial with their
stores, services and types. The pages keep their routes and edition
walls and render the paid panels from there. No visible change.
The core moves from AGPL-3.0 to the Apache License 2.0. The code under
internal/commercial and frontend/src/commercial is licensed under the
Maintenant Commercial Source License: readable and open to
contributions, production use tied to a matching licence key, no
redistribution. NOTICE records the split, COMMERCIAL-LICENSE.md becomes
the customer terms for Personal and Pro keys, and the CLA is dropped.

Source headers become two-line SPDX headers, README and the OCI image
label follow.
@btouchard
btouchard merged commit fa25b92 into main Sep 29, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant