release: 0.4.0 #253
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| # Weekly, for the advisory scan. An earlier commit message claimed a scheduled run | |
| # and there was none: `cargo audit` only ran on a push or a PR, so an advisory | |
| # published after a merge would not have surfaced until the next unrelated change. | |
| # Monday 06:00 UTC. | |
| schedule: | |
| - cron: "0 6 * * 1" | |
| workflow_dispatch: | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| # Nothing in this workflow needs a token beyond checkout. Without this block every | |
| # job inherits the repository default, which on many repositories is read/write on | |
| # all scopes. | |
| permissions: | |
| contents: read | |
| jobs: | |
| # The gate CLAUDE.md states, which CI did not previously enforce: formatting and | |
| # clippy were a local responsibility, so `main` could carry a tree that failed | |
| # them. One Linux runner is enough; neither check is platform-dependent. | |
| lint: | |
| name: fmt + clippy | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: rust/mumdia | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| # rust-toolchain.toml (in rust/mumdia) pins the exact toolchain and requests | |
| # the rustfmt and clippy components; rustup installs them on first use. | |
| - name: Cache cargo | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| rust/mumdia/target | |
| key: ${{ runner.os }}-cargo-lint-${{ hashFiles('rust/mumdia/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-cargo-lint- | |
| - name: Format | |
| run: cargo fmt --check | |
| # --all-targets so tests and benches are linted too: dead code reachable only | |
| # from a test module still fails the build under -D warnings. | |
| - name: Clippy | |
| run: cargo clippy --workspace --all-targets --locked -- -D warnings | |
| # Rustdoc, warnings as errors. Not previously run anywhere, and it was failing: | |
| # four bracketed prose expressions (`[i0,i1,i2]`, `[0,1]`) were parsed as | |
| # intra-doc links to items that do not exist, and three public doc comments | |
| # linked to private items. Cheap to keep green, and the docs are this project's | |
| # contract. | |
| - name: Rustdoc | |
| env: | |
| RUSTDOCFLAGS: -D warnings | |
| run: cargo doc --workspace --no-deps --locked | |
| # Dependency advisories. SECURITY.md declares these in scope and invites pin-bump | |
| # pull requests, so the project needs a way to see them: without this job, two | |
| # CVSS 7.5 advisories sat on the mzML parser (quick-xml, reached through mzdata) | |
| # through every green CI run. The schedule matters as much as the PR trigger, | |
| # because an advisory published after a merge would otherwise never surface. | |
| audit: | |
| name: cargo audit | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: rust/mumdia | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Cache cargo-audit | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ~/.cargo/bin/cargo-audit | |
| key: ${{ runner.os }}-cargo-audit-bin | |
| - name: Install cargo-audit | |
| run: command -v cargo-audit || cargo install cargo-audit --locked | |
| # Vulnerabilities fail the build. Unmaintained-crate warnings do not: `paste` | |
| # is a compile-time proc macro whose 1.0.15 is its final release, so there is | |
| # nothing to bump to and a hard failure would only train people to ignore | |
| # this job. Deny the class that has a fix. | |
| - name: Audit | |
| run: cargo audit --deny unsound --deny yanked | |
| # The desktop application has its own lockfile, which the step above does not | |
| # read (docs/29, work package C). One advisory is ignored deliberately: | |
| # RUSTSEC-2024-0429, an unsound iterator in glib 0.18's `VariantStrIter`. Tauri | |
| # 2's Linux toolkit is gtk 0.18, which pins glib 0.18; the fix is glib 0.20; and | |
| # the application never touches a GVariant. Drop the ignore when Tauri moves to | |
| # gtk4 / glib 0.20. | |
| - name: Audit the desktop lockfile | |
| run: >- | |
| cargo audit --file ../../desktop/Cargo.lock --deny unsound --deny yanked | |
| --ignore RUSTSEC-2024-0429 | |
| build-test: | |
| name: build + test (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| defaults: | |
| run: | |
| working-directory: rust/mumdia | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Cache cargo | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| rust/mumdia/target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('rust/mumdia/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-cargo- | |
| - name: Build (release) | |
| run: cargo build --release --locked | |
| # --workspace is explicit: the release binary is only one member, and the | |
| # config and IO contracts live in the other two. | |
| - name: Test | |
| run: cargo test --workspace --locked | |
| # The only end-to-end coverage there is. The Rust suite builds its inputs in | |
| # process and starts at `extract`, so mzML parsing, the library build, the `run` | |
| # orchestrator, the manifest, RT calibration on real anchors, and quant/report | |
| # writing files were all untested. The fixture is generated, not committed: it is | |
| # built from `test_data/fixture.fasta` and from the library the engine itself | |
| # derives from it, so the planted peaks cannot disagree with the mass model. | |
| smoke: | |
| name: end-to-end smoke (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest] | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Install pyarrow | |
| run: python -m pip install --quiet pyarrow | |
| - name: Cache cargo | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| rust/mumdia/target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('rust/mumdia/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-cargo- | |
| - name: Build the binary | |
| working-directory: rust/mumdia | |
| run: cargo build --release --locked --bin mumdia | |
| # docs/23 is generated from `--help`, so it can only be checked where a | |
| # binary exists. Running it on both matrix legs is also a free | |
| # cross-platform check on the help text: the generator normalises the | |
| # binary name, so a Windows and a Linux build must produce identical output. | |
| - name: CLI reference is current | |
| shell: bash | |
| run: python ci/gen_cli_reference.py --check | |
| # bash on the Windows runner too, so one script covers both platforms, and | |
| # invoked through `bash` rather than relying on the file mode: the execute bit | |
| # is meaningless on the Windows runner, and a checkout that loses it would | |
| # otherwise fail here with "Permission denied" rather than with a test result. | |
| - name: Smoke test | |
| shell: bash | |
| run: bash ci/smoke.sh | |
| - name: Upload output hashes | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: smoke-hashes-${{ matrix.os }} | |
| path: smoke_output_hashes.txt | |
| if-no-files-found: error | |
| # The native pipeline is byte-reproducible across operating systems, not only | |
| # across runs on one machine: measured 2026-08-27, `peptides.tsv` and | |
| # `proteins.tsv` hash identically on Windows and Linux down to the quantity | |
| # digits. Nothing enforced that, so a platform-dependent float reduction or a | |
| # HashMap-order sum could reintroduce a difference silently. | |
| # | |
| # The two platforms are compared against each other rather than against a | |
| # committed hash. A golden value would have to be updated by every legitimate | |
| # change to scoring, which turns an improvement into a chore and the check into a | |
| # rubber stamp. | |
| # Real sidecar imports, in a real environment. | |
| # | |
| # Every torch/mokapot/DeepLC test in `tests/python` SKIPS on the runner, so the entire | |
| # external-classifier contract that `rescore.strict` exists to protect executed nowhere | |
| # on a pull request -- and that is where the worst defect in this project's history | |
| # lived, when mokapot returned a targets-only table and the engine reported ~331,000 | |
| # "identified" peptides against a correct figure near 51,000. The DeepLC import-order | |
| # failure (`OSError: [WinError 1114] ... c10.dll`) also only shows up against a real | |
| # torch-backed DeepLC, and it has shipped once. | |
| # | |
| # Runs only when the sidecars, their environments or the image definition change, so | |
| # the ~10 minute conda solve is not paid on every documentation PR. | |
| sidecar-imports: | |
| name: real sidecar imports (${{ matrix.env_name }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| # One job per environment, NOT two `setup-micromamba` steps in one job. The | |
| # action registers a `micromamba-shell` wrapper and removes it in its post-job | |
| # cleanup, so two invocations in one job leave two cleanups racing for one file | |
| # and the second fails with | |
| # `ENOENT: ... lstat '.../setup-micromamba/micromamba-shell'` -- after all the | |
| # actual work has passed, which is the most misleading way for a job to fail. | |
| matrix: | |
| include: | |
| # The import ORDER is the contract: `deeplc` must come before numpy/pyarrow, | |
| # because DeepLC 4.x is torch-backed and the wrong order aborts torch DLL | |
| # initialisation on Windows (`OSError: [WinError 1114] ... c10.dll`). Importing | |
| # it first here is what proves the worker files still order it that way. | |
| - env_name: deeplc | |
| env_file: env/docker-deeplc.yml | |
| import_check: >- | |
| import deeplc, numpy, pandas, pyarrow, torch, psm_utils; | |
| print('deeplc', deeplc.__version__, '| torch', torch.__version__) | |
| - env_name: rescore | |
| env_file: env/docker-rescore.yml | |
| import_check: >- | |
| import mokapot, ms2pip, numpy, pyarrow; | |
| print('mokapot', mokapot.__version__) | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| - name: Do the sidecars, envs or Dockerfile change? | |
| id: touched | |
| env: | |
| EVENT: ${{ github.event_name }} | |
| run: | | |
| set -euo pipefail | |
| # A scheduled or manually dispatched run ALWAYS resolves the environments. | |
| # The point of the weekly run is the vulnerability sweep below, and the | |
| # advisory database moves on its own; gating it on a diff meant it looked at | |
| # `HEAD~1..HEAD` on main, which is almost never a sidecar change, so the | |
| # sweep would have skipped nearly every week while appearing to be scheduled. | |
| if [ "$EVENT" = "schedule" ] || [ "$EVENT" = "workflow_dispatch" ]; then | |
| echo "run=true" >> "$GITHUB_OUTPUT" | |
| echo "$EVENT run: resolving the environments regardless of the diff" | |
| exit 0 | |
| fi | |
| base="${{ github.event.pull_request.base.sha || 'HEAD~1' }}" | |
| if git diff --name-only "$base" HEAD \ | |
| | grep -Eq '^(scripts/|env/|tests/python/|Dockerfile$)'; then | |
| echo "run=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "run=false" >> "$GITHUB_OUTPUT" | |
| echo "no sidecar-relevant change; skipping" | |
| fi | |
| - uses: mamba-org/setup-micromamba@f457c30a868e4760d3a6fcea5f25dc655b8edf39 # v3.2.1 | |
| if: steps.touched.outputs.run == 'true' | |
| with: | |
| micromamba-version: latest | |
| environment-file: ${{ matrix.env_file }} | |
| environment-name: ${{ matrix.env_name }} | |
| cache-environment: true | |
| - name: Import the sidecar dependencies, in the order the workers use | |
| if: steps.touched.outputs.run == 'true' | |
| shell: micromamba-shell {0} | |
| env: | |
| IMPORT_CHECK: ${{ matrix.import_check }} | |
| run: python -c "$IMPORT_CHECK" | |
| # The Python dependency surface has no Dependabot coverage: the pins live in the | |
| # pip sections of the conda specifications, which Dependabot cannot parse (see | |
| # .github/dependabot.yml). This is what covers it instead, and it audits the | |
| # RESOLVED environment, so transitive packages no specification names are | |
| # included. | |
| # | |
| # Strict on the weekly schedule and on a manual dispatch; advisory on a pull | |
| # request and on a push to main. A new advisory lands independently of any | |
| # change, so a strict gate here fails changes that did not cause it, which is | |
| # how teams end up deleting the scanner. It also blocked a release: the merge | |
| # of #54 went red on main for a setuptools advisory (2026-09-06), and the | |
| # release gate requires a successful ci.yml run for the tagged commit. The | |
| # weekly run is where a new advisory has to be dealt with, and it fails loudly. | |
| - name: Audit the resolved environment for known vulnerabilities | |
| if: steps.touched.outputs.run == 'true' | |
| shell: micromamba-shell {0} | |
| continue-on-error: ${{ github.event_name != 'schedule' && github.event_name != 'workflow_dispatch' }} | |
| run: | | |
| set -euo pipefail | |
| python -m pip install --quiet pip-audit | |
| # NOT --strict, and the reason is specific rather than a shrug. --strict | |
| # fails when a package cannot be resolved on PyPI at all, and `torch` is | |
| # installed from the PyTorch index as `2.14.0+cpu`, a local version that by | |
| # construction does not exist on PyPI. That is not a finding, it is how CPU | |
| # torch is distributed, and failing on it would train everyone to ignore | |
| # this job. Real advisories still fail the run. | |
| # | |
| # The gap this leaves is torch itself. Watch | |
| # https://github.com/pytorch/pytorch/security/advisories directly; nothing | |
| # here can audit a wheel PyPI has never seen. | |
| pip-audit --progress-spinner off | |
| # The exact package set the audit above ran against. The specifications pin the | |
| # tools that matter (mokapot, ms2pip, deeplc, torch) and leave the scientific | |
| # stack to the resolver, so this is the only record of what a given build | |
| # actually installed. Retained per build rather than committed: a lockfile that | |
| # nothing installs from drifts from the specification that does. | |
| - name: Record the resolved package set | |
| if: steps.touched.outputs.run == 'true' | |
| shell: micromamba-shell {0} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p resolved | |
| python -m pip freeze --all > "resolved/pip-freeze-${{ matrix.env_name }}.txt" | |
| micromamba list -n "${{ matrix.env_name }}" \ | |
| > "resolved/conda-list-${{ matrix.env_name }}.txt" || true | |
| wc -l resolved/* | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| if: steps.touched.outputs.run == 'true' | |
| with: | |
| name: resolved-env-${{ matrix.env_name }} | |
| path: resolved/ | |
| retention-days: 90 | |
| - name: Worker contract tests, in a real environment | |
| if: steps.touched.outputs.run == 'true' | |
| shell: micromamba-shell {0} | |
| # pytest is installed here rather than added to the runtime env spec: it is a | |
| # test-only dependency and has no business in the published image's | |
| # environment. The point of running the suite in this env is that the tests | |
| # which skip on a bare runner actually EXECUTE -- measured 66 passed / 7 skipped | |
| # in `rescore`, against 61 / 12 on the plain runner. | |
| run: | | |
| set -euo pipefail | |
| python -m pip install --quiet pytest | |
| python -m pytest tests/python -q -rs | |
| # The desktop application. A separate Cargo workspace, so nothing above builds it, | |
| # and without this job it could break while every other check stayed green. | |
| # | |
| # It is not independent of the engine either: the settings schema and both | |
| # requirement files are compiled into it with `include_str!`, so a change to | |
| # `config.rs` that regenerates `configs/config-schema.json` reaches this crate. | |
| # That coupling is what makes the job worth its minutes. | |
| desktop: | |
| name: desktop app | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| # Tauri links WebKitGTK, whose development headers the runner does not carry. | |
| - name: Install the build dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf | |
| - name: Format | |
| working-directory: desktop/src-tauri | |
| run: cargo fmt --check | |
| - name: Clippy | |
| working-directory: desktop/src-tauri | |
| run: cargo clippy --all-targets -- -D warnings | |
| # Library tests only. The end-to-end tests skip themselves without an engine | |
| # binary, which would make this job look like it covered more than it does; a | |
| # real bundle is exercised by the release rehearsal instead. | |
| - name: Unit tests | |
| working-directory: desktop/src-tauri | |
| run: cargo test --lib | |
| # The frontend has no build step, so this is its whole check. | |
| - name: Frontend agrees with the backend | |
| run: python ci/check_desktop_ui.py | |
| smoke-cross-platform: | |
| name: cross-platform byte equality | |
| runs-on: ubuntu-latest | |
| needs: smoke | |
| steps: | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| pattern: smoke-hashes-* | |
| path: hashes | |
| - name: Compare | |
| run: | | |
| set -euo pipefail | |
| echo "--- collected ---" | |
| for f in hashes/*/smoke_output_hashes.txt; do echo "== $f"; cat "$f"; done | |
| n=$(ls -d hashes/*/ | wc -l) | |
| test "$n" -ge 2 || { echo "need at least two platforms, got $n"; exit 1; } | |
| first="" | |
| for f in hashes/*/smoke_output_hashes.txt; do | |
| if [ -z "$first" ]; then first="$f"; continue; fi | |
| diff "$first" "$f" || { | |
| echo "outputs differ between platforms; see the hashes above" >&2 | |
| exit 1 | |
| } | |
| done | |
| echo "all platforms produced byte-identical peptides.tsv and proteins.tsv" | |
| sidecars: | |
| name: python sidecars + configs | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.12" | |
| # The Python workers are invoked over a positional-file contract and are not | |
| # exercised by the Rust suite, so a syntax error in one of them surfaces only | |
| # mid-run, hours in. This is the cheap floor, not sidecar validation: real | |
| # DeepLC, mokapot, nn_torch and MBR behavior is still untested in CI. | |
| - name: Compile sidecars | |
| run: python -m compileall -q scripts ci | |
| # The desktop application installs env/console-*.txt with uv, which resolves | |
| # differently from pip and from conda, and nothing else in CI reads those files. | |
| # A v0.3.0 installer shipped unable to resolve its primary environment at all. | |
| - name: Install uv | |
| run: python -m pip install --quiet uv | |
| - name: The desktop Python environments resolve | |
| run: python ci/check_console_envs.py | |
| # A config is an artifact the engine must accept. `Config` is | |
| # deny_unknown_fields, so the Rust test `shipped_configs_parse` is the real | |
| # check; this one catches plain JSON syntax errors in any tracked config, | |
| # including ones that test does not list. | |
| - name: Parse tracked JSON | |
| run: | | |
| set -euo pipefail | |
| for f in $(git ls-files '*.json'); do | |
| python -c "import json,sys; json.load(open(sys.argv[1]))" "$f" \ | |
| || { echo "invalid JSON: $f"; exit 1; } | |
| echo "ok $f" | |
| done | |
| # Both YAML checks below need it. It used to be installed inside "Parse env | |
| # specs", so adding an earlier YAML step broke with ModuleNotFoundError: the | |
| # dependency belongs to the job, not to whichever step reached it first. | |
| - name: Install the YAML parser | |
| run: python -m pip install --quiet pyyaml | |
| # A workflow that GitHub refuses to parse fails when it is DISPATCHED, not when | |
| # it is pushed: for release.yml that is the moment a release is attempted. And | |
| # PyYAML accepts a duplicate mapping key silently, so a local parse check does | |
| # not see the one error that actually reached the remote (an orphaned `exe: ""` | |
| # left behind by deleting a matrix entry). | |
| - name: Check the workflows GitHub has to parse | |
| run: python ci/check_workflows.py | |
| # The conda specs are the only installation instructions for the sidecars; | |
| # a malformed one fails the Docker build rather than CI. | |
| - name: Parse env specs | |
| run: | | |
| set -euo pipefail | |
| for f in $(git ls-files 'env/*.yml' 'docker/*.yml'); do | |
| python -c "import yaml,sys; yaml.safe_load(open(sys.argv[1]))" "$f" \ | |
| || { echo "invalid YAML: $f"; exit 1; } | |
| echo "ok $f" | |
| done | |
| # The workers are invoked over a positional-file contract that nothing else | |
| # checks. Tests needing torch, mokapot, deeplc or ms2pip skip here rather | |
| # than fail, which is honest but means CI covers the contracts that need only | |
| # numpy and pyarrow: the whole mbr_worker suite, including the regression for | |
| # the q-column bug that left 34,280 of 34,664 transfers unquantified, the | |
| # library-recipe encoding contracts, and the import-order checks. | |
| - name: Sidecar contract tests | |
| run: | | |
| python -m pip install --quiet -r tests/python/requirements.txt | |
| python -m pytest tests/python -q -rs | |
| # docs/24 is generated from config.rs, so a new field or a changed default | |
| # must land with its documentation or CI fails. A reference nobody | |
| # regenerates is worse than none, because it reads as current. | |
| # One parse of config.rs produces two artifacts: the reference document a | |
| # person reads, and configs/config-schema.json, which the desktop settings | |
| # editor renders its form from. They go stale together, so --check covers both. | |
| - name: Config reference and schema are current | |
| run: python ci/gen_config_reference.py --check | |
| # The release binary is statically linked, so it carries 173 third-party crates | |
| # whose Apache-2.0 and MIT terms require their notices to ship with it. Generated | |
| # from Cargo.lock, so a dependency change that alters the licence inventory has to | |
| # land with the regenerated notice file. | |
| # Both generators below read the local cargo registry, and this job never | |
| # compiles anything, so on a fresh runner the registry is empty. Without this | |
| # step the licence generator recovered zero per-crate notices and `--check` | |
| # reported the committed file as stale: a difference in the environment, not in | |
| # the tree. `cargo fetch` populates `registry/cache` with the .crate tarballs, | |
| # which the generator reads directly, so its output is the same here as on a | |
| # machine that has built. | |
| - name: Fetch the crate sources the generators read | |
| run: cargo fetch --locked --manifest-path rust/mumdia/Cargo.toml | |
| - name: Third-party licences are current | |
| run: python ci/gen_third_party_licenses.py --check | |
| # THIRD_PARTY_LICENSES.md is a notice document for a human reader. The SBOM | |
| # answers the machine question: given a published binary, which exact versions | |
| # are inside it and how are they connected. That is what a vulnerability | |
| # scanner and an institutional software inventory consume, and prose cannot be | |
| # queried. Generated from the same lockfile, so both move together. | |
| - name: The SBOM is current | |
| run: python ci/gen_sbom.py --check | |
| # Source comments cite the document that specifies each behavior. Several of | |
| # those documents are local-only design notes, so a public clone used to hold | |
| # ~141 references to files it does not ship. | |
| - name: Check documentation references | |
| run: python ci/check_doc_refs.py |