Feature Specification: License Report

Created: 2026-10-24

Status: Implemented, with the decisions in decisions.md

Input: Reconstructed from the code (core/setups/nixos/sbom.nix, core/setups/nixos/license-report.nix, core/setups/nixos/license-report.py, core/setups/nixos/license-allowlist.toml, core/checks/license-report/) and from the SBOMs of the 30 nodes of core.

User Scenarios & Testing (mandatory)

Each node has a software bill of materials (SBOM, system.build.sbom) and a license report (system.build.licenseReport). The report compares the license of each component of the SBOM with license-allowlist.toml. CI builds both next to the toplevel of each node.

User Story 1 - Distribute a node with licenses that permit it (Priority: P1)

A hosting provider distributes the software of a node, or gives access to it. The provider wants to know that each component has a license that permits this together with the code of this repository, which has the EUPL-1.2.

Why this priority: a component with a license that does not permit the distribution makes the distribution of the node a copyright infringement.

Independent Test: core/checks/license-report runs the report on a fixture SBOM and compares the verdict of each component.

Acceptance Scenarios:

  1. Given a component with a license that [licenses] covers, When the report runs, Then the verdict is allowed.
  2. Given a component with a license that [licenses] does not cover, and that is not only NOASSERTION, When the report runs, Then the verdict is not-allowed and the build of the report fails.
  3. Given a component without license data that [components] does not give, When the report runs, Then the verdict is unknown and the build does not fail.
  4. Given a component without license data that is a script of writeShellScript in the configuration, When the report runs, Then its license is EUPL-1.2. A package without license data keeps NOASSERTION.

User Story 2 - Decide the open licenses (Priority: P2)

A maintainer wants a recommendation for each license that the matrix does not give and that a node has, with the components that have it.

Why this priority: a license without a decision makes the report fail. Thus a change that adds a component with such a license needs a decision first.

Independent Test: the license report of each node builds, and each license of decisions.md has a decision.

Requirements (mandatory)

Functional Requirements

  • FR-001: The SBOM MUST give each store path of the runtime closure of the toplevel. The licenses come from meta.license in nixpkgs, and for a Ruby gem from its gemspec.
  • FR-002: The SBOM MUST find the derivations of the packages that the closure gets only through strings: the packages of the system, of the units and of the file systems, the locales, the options of the type package of each enabled service, and the derivations in the string context of the files of /etc and of the scripts, the environment and the serviceConfig of each unit.
  • FR-003: The report MUST join the licenses of a list with AND, but the list of "the same terms as Perl itself" (Artistic-1.0 and GPL-1.0-or-later) with OR.
  • FR-004: The report MUST accept X-N-or-later (or X-N+) when [licenses] has X-M with M not less than N.
  • FR-005: For X WITH Y, the report MUST accept X WITH Y or X.
  • FR-006: The report MUST give a component without license data the license NOASSERTION.
  • FR-007: [components] MUST give a component with the one license that its row names. A change of that license makes the verdict not-allowed.
  • FR-008: The exit status MUST be 1 if a component is not-allowed, else 0. A component is unknown when [licenses] covers its license except for NOASSERTION, and not-allowed when [licenses] and [components] do not cover it in other ways.
  • FR-009: The report MUST give a component without license data the license of this repository (EUPL-1.2) when the component is an output of a derivation that writes a text (writeTextFile and the functions that use it, for example writeShellScript) and that a string of the units or of /etc refers to directly. A store path that is not such an output keeps NOASSERTION.
  • FR-010: The SBOM MUST give each npm package in a node_modules directory of the closure, and each Rust crate in the vendor directory (cargoDeps) of a Rust package of the roots in sbom.nix, with the license that its package.json or Cargo.toml declares. The SBOM MUST give each Go module that a program of a Go package of the roots links, with the licenses that identify_license finds in the license files of the module (concluded, not declared). The report MUST give such a component with the packages that include it.

Key Entities

  • [licenses]: the licenses that the report accepts, each with its source (the EUPL compatibility matrix of the European Commission, or its EUPL FAQ).
  • [components]: components that the report accepts with one license, each with a reason.
  • Text derivations (system.build.sbomTextDrvs): the .drv files that the strings of the units and of /etc refer to directly, other than the packages of pkgs, one on each line. The report reads them at build time to find the outputs of the derivations that write a text (FR-009).

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: core/checks/license-report and core/checks/sbom-vendored pass.
  • SC-002: The license report of each node builds.

Assumptions

  • The components of a node are separate programs that the node distributes together (aggregation), or libraries that a program links dynamically. See "Relations between components" in decisions.md.
  • The meta.license of nixpkgs is correct. Where it is not, [components] gives the license and the reason.

Known Deviations

  • The SBOM has no license data for some components. Their verdict is unknown, which does not make the report fail. decisions.md gives the causes and the fixes.
  • The SBOM does not give the crates of a Rust package or the Go modules of a Go package that is not a root in sbom.nix (for example librsvg), the Go modules of a Go package without a module directory (vendorHash = null), the Go standard library, or the npm packages in the bundles of a front end (for example public/packs of Mastodon). Thus the report does not check their licenses. The vendor directory of a Rust package has the crates of all platforms, thus the SBOM can give more crates than the build links. See "The dependencies that Go, Rust and npm builds vendor" in decisions.md.
  • The license of a Go module comes only from the license files in the root directory of the module, and is all the licenses of these files with AND. A license file that lists the licenses of third-party code (for example the LICENSE of github.com/hashicorp/vic) thus gives all of them, also when the module does not include that code. A row in [components] gives the license of such a module (for example github.com/hashicorp/vic and github.com/opencontainers/go-digest).
  • nixpkgs does not tell if a list of licenses is AND or OR. The report uses AND, which is the strict choice.