Files
continuum-common/docs/LICENSES.md
T
octoturge 249b54e8c4 Record the dual-licence elections continuum-proxy was relying on
Rule one at the top of this document says to record an election so it is not
re-litigated, and three dependencies in continuum-proxy had never had theirs
written down. All three already passed `cargo deny check licenses`; the point
is that a grep for "GPL" turns up an answer instead of an argument.

- `unescaper` 0.1.10 is `MIT OR GPL-3.0-only`, reached through
  tokio-serial -> serialport, so it is genuinely in the Linux build.
- `r-efi` 6.0.0 offers LGPL-2.1-or-later as one of three options, but only
  compiles for UEFI targets - noted because the next person to grep will
  find it and should not have to work that out again.
- `serialport` 4.10.0 is MPL-2.0, which the table allows because the
  copyleft is per-file. That holds only while we do not edit its files, so
  the condition is now written next to the permission.

Also records the eight crates added to continuum-proxy on 2026-10-15 for
reading `.3mf` archives, and why the zip feature set is deflate-only: the
other codec features pull in bzip2, lzma/xz and zstd bindings, each another
licence to check for no benefit.

Verified from `cargo metadata` and `cargo tree --invert`, not from memory.
2026-10-15 21:32:24 +00:00

9.0 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.

Audit, 2026-10-15 - continuum-proxy dual-licence elections

Found while adding a zip reader to continuum-proxy (see below). All three were already in the tree, all three pass cargo deny check licenses, and none had its election recorded - which rule one at the top of this document says to do, so that a grep for "GPL" does not restart the argument. Verified from cargo metadata, not from memory.

unescaper 0.1.10 - MIT OR GPL-3.0-only, we elect MIT. Reached via tokio-serial -> serialport -> unescaper, so it is in the Linux build. The MIT half is ours to take.

r-efi 6.0.0 - MIT OR Apache-2.0 OR LGPL-2.1-or-later, we elect MIT or Apache-2.0. Reached via native-tls -> tempfile -> getrandom, but only on UEFI targets: it is not compiled for any platform we ship. The LGPL option would be the problem one if it ever were, and electing around it is free.

serialport 4.10.0 - MPL-2.0, allowed with a condition. The table above permits MPL-2.0 because its copyleft is per-file, which holds only while we do not edit the dependency's own files. We do not and should not: if serialport ever needs a patch, vendor the change as a fork with its own notice, or wrap it, rather than editing files in place.

New in continuum-proxy on 2026-10-15, all clear: zip 9.0.3 (MIT), flate2 1.1.10, crc32fast 1.5.2, typed-path 0.12.3 (all MIT OR Apache-2.0), miniz_oxide 0.9.1 (MIT OR Zlib OR Apache-2.0), adler2 2.0.1 (0BSD OR MIT OR Apache-2.0), simd-adler32 0.3.10 (MIT), zlib-rs 0.6.8 (Zlib). Added for reading .3mf project archives. The feature set is deliberately deflate-flate2 only - zip's other codec features pull in bzip2, lzma/xz and zstd bindings, none of which are needed and each of which is another licence to check. Confirmed none of them entered the graph.

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.