# 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: ```sh 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. **An HTTP client in `continuum-proxy`, and the version we could not take.** Added 2026-10-15 so a gateway can download a job file from a presigned URL. The choice is `reqwest` 0.12.28 with `default-features = false` and `rustls-tls-native-roots`, and the version matters: - **reqwest 0.13 is out.** Its `rustls` feature pulls `rustls-platform-verifier` -> `webpki-root-certs` under **CDLA-Permissive-2.0**, which is not in the table above, and `cargo deny` rejects it outright (`error[rejected]: license is not explicitly allowed`). CDLA-Permissive-2.0 is in fact a permissive licence and could reasonably be added - but it is a *data* licence applied to a certificate bundle, which is a different question from a code licence and deserves a deliberate decision rather than being waved through to unblock a dependency bump. Left out until someone wants it. - The other 0.13 route brings `aws-lc`, which contains OpenSSL-licensed C. Also out. What 0.12 with that feature set brings, all verified from `cargo metadata` rather than from memory, and all permissive: `reqwest` 0.12.28, `hyper` 1.12.0, `hyper-util` 0.1.21, `http-body` 1.1.0, `tower` 0.5.3, `tower-service` 0.3.3 (all MIT); `rustls` 0.23.46, `hyper-rustls` 0.27.10, `rustls-native-certs` 0.8.5 (`Apache-2.0 OR ISC OR MIT`); `rustls-webpki` 0.103.15 and `untrusted` 0.9.0 (ISC); `ring` 0.17.14 (**`Apache-2.0 AND ISC`** - note the AND: both apply, and both are fine); `sync_wrapper` 1.0.2 (Apache-2.0); `subtle` 2.6.1 (BSD-3-Clause); `zerocopy` 0.8.56 (`BSD-2-Clause OR Apache-2.0 OR MIT`); plus `http`, `httparse`, `base64`, `url`, `ipnet`, `rustls-pki-types` and `tokio-rustls` (`MIT OR Apache-2.0`). `openssl-probe` 0.2.1 appears and is only a certificate-path lookup, no C. Note that `openssl-sys` is still in the tree regardless, via the existing `native-tls` the Bambu MQTT and FTPS clients use - rustls here does not remove it. **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.