Skip to content

ci(release): ship licence texts and third-party notices; link self_encryption only with test-utils - #243

Merged
jacderida merged 2 commits into
WithAutonomi:mainfrom
grumbach:ci/release-third-party-notices
Oct 2, 2026
Merged

jacderida merged 2 commits into
WithAutonomi:mainfrom
grumbach:ci/release-third-party-notices

Conversation

@grumbach

@grumbach grumbach commented Oct 1, 2026

Copy link
Copy Markdown
Member

Linear issue

Closes V2-1390
Closes V2-1391

Risk tier

  • T0 — docs / tooling / CI / pure UX-output. Repo CI only.
  • T1 — client-only, no network-facing behavior change. CI + prod compat smoke.
  • T2 — node/client logic with behavioral surface, no protocol/format/economics change. Dev testnet + ADR.
  • T3 — protocol / storage format / payments / routing. T2 evidence + adversarial testing.

No node behaviour changes. One optional dependency moves between Cargo features, so the default dependency graph loses a crate no release code calls, and the release workflow adds licence files to the archives. Reviewers may prefer to raise the tier because archive contents change.

Compatibility

  • Wire: none
  • Storage: none
  • API: none. Devnet::publish_public_file, the only code that uses self_encryption, already required test-utils; enabling test-utils now also pulls in self_encryption. Release archives gain LICENSE-MIT, LICENSE-APACHE, THIRD-PARTY-NOTICES.txt and RUST-STD-COPYRIGHT.html; the auto-upgrade extractor (src/upgrade/apply.rs) and ant-client's node installer pick the binary by file name and skip other entries, so nodes and clients upgrading to these archives are unaffected.

Semver impact

  • breaking
  • feature
  • fix

Test evidence

Two commits.

1. self_encryption only with test-utils. The default webrtc-direct feature enabled dep:self_encryption, but every use is inside Devnet::publish_public_file (#[cfg(all(feature = "webrtc-direct", feature = "test-utils"))]) and the test-utils-gated parts of tests/webrtc_direct_devnet.rs. Release builds therefore compiled a GPL-3.0 crate (published self_encryption releases carry a linking exception) that no code called, and licence scanners listed it against every shipped binary.

  • cargo tree -e normal -i self_encryption --target <t> with default features finds no match on the release targets; with --features test-utils it resolves self_encryption v0.36.0.
  • cargo clippy --all-targets --all-features -- -D warnings (the CI command), cargo fmt --check and RUSTDOCFLAGS=-D warnings cargo doc --no-deps pass. Library and binaries compile for default, no-default, no-default + webrtc-direct, no-default + test-utils, no-default + both, test-utils and all features. (--all-targets with no-default + test-utils fails in tests/e2e on a missing tracing_subscriber, identically on unmodified main.)
  • cargo test --test webrtc_direct_devnet: 3 passed with default features, 6 with --features test-utils, and the ignored five-node test that self-encrypts a file passed against a local Anvil.

2. Licence texts and third-party notices in release archives. Archives held only the binary and bootstrap_peers.toml, while the binary statically links several hundred third-party crates and bundled C libraries whose licences require their notices in binary redistribution.

  • scripts/third_party_notices/generate.py (Python 3.11+, standard library only) builds THIRD-PARTY-NOTICES.txt from Cargo.lock for a target. It takes the packages cargo tree resolves (normal and build dependencies, so build tools are listed too), matches each to exactly one cargo metadata package by source, and reproduces unaltered every licence, copyright, notice, authors, patents, credits and third-party file each crate ships at any depth, identical texts once, with each crate's exact crates.io source archive as its Source link.
  • Policy: config.toml lists the licences the release may redistribute under. Each crate's SPDX expression is parsed; the notice records the licence it is redistributed under, which must be allowed and whose text must be identified in one of the crate's own licence files (licence files of bundled components do not count). Gaps fail generation: no licence text, an unreviewed licence, an ambiguous package, or a crate that may contain native code (C, C++, Objective-C, CUDA or assembly sources, prebuilt libraries, a cc/cmake/nasm build dependency, a links key, or a *-src crate) without a reviewed entry.
  • Gaps found and handled with reviewed, version-pinned entries: saorsa-pqc 0.5.2, siphasher 1.0.3, lmdb-master-sys 0.2.6 and five macOS objc2 crates publish no text of their licence anywhere (crate or repository at the published commit), so a canonical text is supplied with a checksum. LMDB (OpenLDAP Public License 2.8), mimalloc, zstd, bzip2, XZ Utils, AWS-LC, ring and BLAKE3 have reviewed native entries; so do a few build tools and test fixtures. saorsa-transport 0.37.0's published crate contains a stray .minimax/ folder with .NET DLLs (removed in its own PR); nothing from it is compiled.
  • For the musl Linux builds the notices append musl's COPYRIGHT for the release the building rustc uses: the version is read from rust-lang/rust's src/ci/docker/scripts/musl.sh at that rustc's commit, and the text from that musl release tarball (musl 1.2.5 for Rust 1.98). Each build job also copies the toolchain's own Rust standard library notices (share/doc/rust/COPYRIGHT-library.html) into the archive as RUST-STD-COPYRIGHT.html.
  • Release workflow: each build job generates the notices for its own target before building and packs them with LICENSE-MIT, LICENSE-APACHE and RUST-STD-COPYRIGHT.html; the Windows signing job repackages the same files and fails if any is missing. CI generates the notices for all five targets on every pull request.
  • Local runs on this branch: linux-musl x64 496 crates / 327 texts, linux-musl arm64 494 / 326, macOS x64 506 / 333, macOS arm64 504 / 332, Windows 505 / 319. A cross-check comparing every Copyright line in every crate's licence files against the output found none missing on any target. An earlier attempt with cargo-about replaced 85 crates' licence files with generic templates (Copyright (c) <year> <copyright holders>), which is why this uses a verbatim generator. Workflow YAML parses; archive commands were exercised locally with a placeholder binary. The release workflow itself only runs on a tag.
  • Question for counsel, not a blocker: whether the exact crates.io source links are a sufficient source offer for the MPL-2.0 components (attohttpc, option-ext).

New dependency

none (the generator is a standard-library Python script run in CI; actions/setup-python pins Python 3.12 in the release build jobs)

ADR

n/a

Mitigation / rollback

Revert either commit; neither touches runtime code, and the release workflow returns to the previous archive contents.

The default `webrtc-direct` feature enabled `dep:self_encryption`, but the
only code that calls it is `Devnet::publish_public_file`, which is compiled
only with `test-utils` (it seeds public files for the browser devnet tests).
Release builds therefore compiled self_encryption without using it, and the
crate appeared in the dependency graph of every shipped binary. The published
self_encryption releases are GPL-3.0 with a linking exception, so licence
scanners report it against ant-node even though no code reaches the binary.

Move the optional dependency to `test-utils`. Default and release builds no
longer resolve self_encryption on any release target; `test-utils` builds
and the five-node browser devnet test that self-encrypts a file are
unchanged. No public API changes: the one function that uses the crate
already required `test-utils`.
Release archives contained only the binary and bootstrap_peers.toml. The
binary statically links several hundred third-party crates and some bundled
C libraries whose licences (MIT, BSD, ISC, Apache-2.0, MPL-2.0, OpenLDAP and
others) require their copyright and licence notices to accompany binary
redistribution, and the archives did not carry ant-node's own licence files.

scripts/third_party_notices/generate.py builds THIRD-PARTY-NOTICES.txt from
Cargo.lock for one or more targets. It takes the packages `cargo tree`
resolves (normal and build dependencies, so build tools are listed too),
matches each to exactly one `cargo metadata` package, and for each:

- reproduces, unaltered, every licence, copyright, notice, authors, patents,
  credits and third-party file the crate ships at any depth, printing
  identical texts once, and names the crate's exact crates.io source archive,
  which is also how source is offered where a licence requires it (MPL-2.0);
- checks the declared licence against the policy in config.toml and records
  the licence it is redistributed under, which must be allowed and whose
  text must be identified in one of the crate's own licence files (those of
  bundled components do not count);
- when a crate ships no such files, or none with that licence's text, takes
  them from its repository at the commit recorded in the crate (or Cargo's
  checkout, for a git dependency), and failing that from a reviewed,
  version-pinned config entry with a checksummed canonical text
  (saorsa-pqc, siphasher, lmdb-master-sys and five macOS objc2 crates
  today);
- requires a reviewed config entry for any crate that may contain native
  code: one that ships C, C++, Objective-C, CUDA or assembly sources or
  prebuilt libraries, builds with cc/cmake/nasm, declares `links`, or is a
  `*-src` crate (LMDB under the OpenLDAP Public License 2.8, mimalloc, zstd,
  bzip2, XZ Utils, AWS-LC, ring, BLAKE3, and a few build tools and test
  fixtures).

Any gap fails generation. For the statically linked Linux builds it also
appends musl's COPYRIGHT for the musl release the building rustc uses: the
version is read from Rust's own build scripts at that rustc's commit, and
the text from that musl release tarball.

Each release build job generates the notices for its own target and puts
them in the archive with LICENSE-MIT, LICENSE-APACHE and
RUST-STD-COPYRIGHT.html, the Rust standard library's notices copied from the
toolchain that built the binary. The signed Windows archive is repackaged
with the same files. The upgrade path and the client's node installer
extract the binary by name, so the extra entries do not affect them. CI
generates the notices for all release targets on every pull request.
@jacderida

Copy link
Copy Markdown
Member

Reviewed as part of the V2-1385 sweep. This is the strongest PR in the set, and it closes two sub-issues (V2-1390, V2-1391).

On the self_encryption feature move (V2-1390) — I verified this is safe rather than assuming it. Every use site is already gated on both features before this change:

  • src/devnet.rs — publish_public_file and its helpers are #[cfg(all(feature = "webrtc-direct", feature = "test-utils"))]
  • tests/webrtc_direct_devnet.rs — the self_encryption import is #[cfg(feature = "test-utils")]

So moving dep:self_encryption from webrtc-direct to test-utils moves no code at all; it only drops a GPL-3.0 crate out of the default dependency graph. No .rs file needed to change, and none did.

On the notices (V2-1391) — notices are generated per target, and the archives now carry them with LICENSE-MIT, LICENSE-APACHE and RUST-STD-COPYRIGHT.html. Two details I particularly like: the Windows signing job hard-fails if any file is missing from the unsigned archive rather than silently shipping without it, and the comment correctly notes the upgrade path extracts the binary by name so the extra entries are harmless.

The config does the work the issue asked for on the C code Cargo metadata hides — LMDB under OpenLDAP 2.8, mimalloc, zstd (with a clear note on why zstd/COPYING appears despite BSD-3-Clause being the elected licence), bzip2, liblzma — plus the musl appendix resolved from the actual building toolchain.

The part that isn't in either issue, and is the most valuable thing here: because allowed excludes the GPL family and the new notices job runs on every PR with default features, a GPL crate re-entering a release build now fails CI. That turns a one-time cleanup into a standing guard, which is what stops this whole audit from being repeated in six months. Good call.

Two notes for later, neither blocking:

  • generate.py is byte-identical in five repositories with no shared source of truth. Worth extracting; I'll follow up separately.
  • Notices generation reaches out to the GitHub API and musl.libc.org at release time, so a rate limit or outage can now fail a release build. Mitigated by the token and retries, but it is new network dependence in the release path.

Approving.

@jacderida jacderida left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the self_encryption call sites were already gated on both features, so the Cargo change moves no code. The CI notices job doubles as a standing GPL guard.

@jacderida
jacderida merged commit 56b5770 into WithAutonomi:main Oct 2, 2026
20 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.

2 participants