Files
continuum-common/docs/LICENSES.md
T
octoturge d5375254d7 Add the licence policy, and enforce permissive-only dependencies
Continuum is intended to be sold, so every dependency has to permit
distribution in closed-source commercial software. Nothing recorded that
before: our own crates and npm packages carried no licence field at all,
which made `cargo deny` report them as `unlicensed` errors and left the
actual policy nowhere.

docs/LICENSES.md is the policy and lives here because it is the repo both
other tiers already depend on. It covers the allow/deny list, the
2026-09-17 audit of all six repos (no copyleft reaches any binary), and
the three categories `cargo deny` structurally cannot see: external
binaries we spawn or ship, ML model weights, and self-hosted server
software whose licence is not its client library's.

Two findings in there are worth more than the tooling: Redis 7.4+ is
RSALv2/SSPL and 8.0+ adds AGPLv3, and Ultralytics YOLO and Obico's
print-failure model are both AGPL-3.0 - the obvious choice in each case
is the one we cannot ship.

Crates declare `LicenseRef-Proprietary` with `publish = false`; that
string is deliberately not valid SPDX so it must never enter an allow
list. npm packages use `"license": "UNLICENSED"`.
2026-10-03 10:10:08 +00:00

7.2 KiB

Licence policy

Continuum is a closed-source commercial product. We intend to sell it, so every dependency must permit distribution in proprietary software without publishing our own source. This document is the policy; deny.toml in each repo is the machine-enforced half of it.

The rule

Permissive only. A dependency is allowed if it is one of:

Licence Notes
MIT, ISC, 0BSD, BSD-2-Clause, BSD-3-Clause The common case
Apache-2.0 (incl. WITH LLVM-exception) Carries a patent grant, which is a plus
Unlicense, CC0-1.0 Public-domain dedications
Zlib, BlueOak-1.0.0 Permissive, OSI/Blue Oak approved
Unicode-3.0 Unicode data tables
MPL-2.0 File-level copyleft only; fine as long as we do not edit the dependency's own files

Forbidden, no exceptions: GPL, LGPL (when statically linked), AGPL, SSPL, RSAL, Commons Clause, "source available", CC-BY-NC, and any licence with a field-of-use or non-commercial restriction.

Two rules that are easy to get wrong:

  • Dual-licensed dependencies are fine - we elect the permissive option. Record the election here so it is not re-litigated (see node-forge below).
  • LGPL is conditionally fine but only via dynamic linking with the library replaceable by the user. Rust static linking does not qualify. Assume LGPL is forbidden unless someone writes down why a specific case is not.

How it is enforced

Rust, per repo:

cargo deny check licenses                                  # proxy, ai-worker
cargo deny --manifest-path packages/rust-core/Cargo.toml check licenses   # common, schemas
cargo deny --manifest-path apps/desktop/src-tauri/Cargo.toml check licenses  # app

Our own crates declare license = "LicenseRef-Proprietary" with publish = false. LicenseRef-Proprietary is deliberately not a real SPDX identifier and must never be added to a deny.toml allow list - a third-party crate claiming it would be a genuine finding, and publish = false is what marks ours as first-party. Our npm packages use "license": "UNLICENSED" with "private": true, which is npm's equivalent convention.

JavaScript has no cargo deny equivalent wired up yet; the audit below was done by walking installed node_modules manifests.

What the tooling cannot see

cargo deny only reads the cargo graph. Three categories sit outside it and have to be checked by hand:

  1. External binaries we spawn or ship. Invoking a GPL program as a separate process is not itself a derivative work, but shipping it in our installer means distributing it under its own terms.
  2. ML model weights. A model's licence is independent of the runtime that loads it. This is the single biggest trap in this project (see continuum-ai-worker below).
  3. Managed/self-hosted services. The client library's licence is not the server's licence. ioredis being MIT says nothing about Redis.

Audit, 2026-09-17 (Redis resolved 2026-10-03)

All six repos audited. No copyleft code is currently compiled into any Continuum binary. Every Rust crate across continuum-proxy, continuum-ai-worker, continuum-app/apps/desktop, continuum-common and continuum-schemas passes the policy above. The JS graphs are MIT/Apache/ISC/ BSD/BlueOak throughout.

Fixed in this pass: all six of our own crates and all nine of our npm packages were missing a licence field (cargo deny reported them as unlicensed errors), and no repo had a deny.toml.

Findings carried forward

Redis - RESOLVED 2026-10-03, we use Valkey. Redis 7.4+ is RSALv2/SSPLv1 and Redis 8.0+ added AGPLv3 as a third option. All three are non-permissive, and SSPL §13 specifically targets offering the software's functionality as a service - which is what a cloud control plane does. The clients were never the problem (ioredis and the redis crate are both MIT); the server we deploy was.

We now deploy Valkey (BSD-3-Clause, Linux Foundation, forked from Redis 7.2.4). What changed: REDIS_URL -> VALKEY_URL and CONTINUUM_REDIS_URL -> CONTINUUM_VALKEY_URL, the exported redis client in continuum-backend/src/queue/ renamed to valkey, Config.redis_url -> Config.valkey_url in continuum-ai-worker, /health now reports a valkey field, and continuum-backend/docker-compose.yml pins valkey/valkey:8-alpine. Both tiers still typecheck/compile unchanged otherwise, because Valkey speaks the same protocol.

Two things deliberately did not change, so nobody "fixes" them later: the redis:// URL scheme (it names the RESP wire protocol, and no client library understands a valkey:// scheme), and the client libraries themselves (ioredis, the redis crate) - they are RESP clients, and their names refer to the protocol's origin, not to the server we run.

AI model weights - blocks PLAN.md step 4. src/inference/mod.rs currently stubs a "spaghetti" detection. The two obvious off-the-shelf sources are both AGPL-3.0: Ultralytics YOLO (v5/v8/v11) and Obico / The Spaghetti Detective's print-failure model. Neither can ship in a commercial product without a paid enterprise licence. The ort crate itself is MIT and fine. Permissive alternatives to evaluate when step 4 comes up: YOLOX (Apache-2.0), RT-DETR (Apache-2.0), RF-DETR (Apache-2.0), or training our own weights on a permissively-licensed architecture. Decide the architecture before collecting training data - retraining because of a licence is the expensive version of this mistake.

go2rtc and ffmpeg - currently fine, keep it that way. continuum-proxy/src/go2rtc.rs spawns go2rtc (MIT) from PATH and expects the operator to have installed it, so we distribute nothing. If that ever changes: go2rtc shells out to ffmpeg, and ffmpeg is LGPL-2.1+ only when built without --enable-gpl. Most distro and prebuilt ffmpeg binaries are GPL builds (they bundle x264). Shipping one would be a GPL distribution.

node-forge - dual-licensed, we elect BSD-3-Clause. It appears in continuum-app as (BSD-3-Clause OR GPL-2.0). The election is ours to make and is recorded here so the GPL half does not trigger a false alarm later.

caniuse-lite - CC-BY-4.0, attribution required if redistributed. A build-time browserslist dependency in continuum-app; its data is not bundled into the Nuxt output, so no attribution obligation today. It would acquire one if anything started shipping the dataset itself.

Infrastructure already clear. PostgreSQL and pgvector (PostgreSQL licence), PgBouncer (ISC), pgCat (MIT), Supabase (Apache-2.0), ONNX Runtime (MIT), Cloudflare R2 (a service, governed by contract not licence) all permit commercial use.

Not findings

A node_modules walk turns up packages named baz, browser_field, invalid_main, false_main and nw-pre-gyp-module-test with no licence field. These are test fixtures inside other packages, not dependencies. Ignore them.

When adding a dependency

  1. Check the licence against the table above before writing code against it. Replacing a library after it is load-bearing is the costly path.
  2. Run the repo's cargo deny check licenses.
  3. If it is an external binary, a model, or a service, none of the tooling will catch it - add it to the audit section above by hand.