`reqwest` was added to continuum-proxy so a gateway can download a job file from a presigned URL. Everything it brings is permissive and verified from `cargo metadata`; the entry lists each crate so the next person does not have to re-derive it. The useful half is the rejection. reqwest 0.13's `rustls` feature pulls `webpki-root-certs` under CDLA-Permissive-2.0 and `cargo deny` refuses it. That licence is in fact permissive and could reasonably be added to the table - but it is a *data* licence on a certificate bundle, which is a different question from a code licence, and waving it through to unblock a dependency bump is how a policy stops meaning anything. Recorded as a deliberate open question rather than silently allowed or silently forgotten. The other 0.13 route brings `aws-lc` and its OpenSSL-licensed C, which is simply out. Also noted: rustls here does not remove `openssl-sys` from the tree. It is still there via the `native-tls` that the Bambu MQTT and FTPS clients use, so nobody should read this entry as "the proxy no longer links OpenSSL".
218 lines
11 KiB
Markdown
218 lines
11 KiB
Markdown
# 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.
|