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:
- Given a component with a license that
[licenses]covers, When the report runs, Then the verdict isallowed. - Given a component with a license that
[licenses]does not cover, and that is not onlyNOASSERTION, When the report runs, Then the verdict isnot-allowedand the build of the report fails. - Given a component without license data that
[components]does not give, When the report runs, Then the verdict isunknownand the build does not fail. - Given a component without license data that is a script of
writeShellScriptin the configuration, When the report runs, Then its license isEUPL-1.2. A package without license data keepsNOASSERTION.
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.licensein 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
packageof each enabled service, and the derivations in the string context of the files of/etcand of the scripts, the environment and theserviceConfigof 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.0andGPL-1.0-or-later) withOR. - FR-004: The report MUST accept
X-N-or-later(orX-N+) when[licenses]hasX-MwithMnot less thanN. - FR-005: For
X WITH Y, the report MUST acceptX WITH YorX. - 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 verdictnot-allowed. - FR-008: The exit status MUST be 1 if a component is
not-allowed, else 0. A component isunknownwhen[licenses]covers its license except forNOASSERTION, andnot-allowedwhen[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 (writeTextFileand the functions that use it, for examplewriteShellScript) and that a string of the units or of/etcrefers to directly. A store path that is not such an output keepsNOASSERTION. - FR-010: The SBOM MUST give each npm package in a
node_modulesdirectory of the closure, and each Rust crate in the vendor directory (cargoDeps) of a Rust package of the roots insbom.nix, with the license that itspackage.jsonorCargo.tomldeclares. The SBOM MUST give each Go module that a program of a Go package of the roots links, with the licenses thatidentify_licensefinds 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.drvfiles that the strings of the units and of/etcrefer to directly, other than the packages ofpkgs, 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-reportandcore/checks/sbom-vendoredpass. - 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.licenseof 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.mdgives 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 examplelibrsvg), 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 examplepublic/packsof 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" indecisions.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 theLICENSEofgithub.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 examplegithub.com/hashicorp/vicandgithub.com/opencontainers/go-digest). - nixpkgs does not tell if a list of licenses is
ANDorOR. The report usesAND, which is the strict choice.