Skip to content

feat(stack): warn on LLVM-family site-layer packages and write a backport list - #11

Merged
HaoZeke merged 2 commits into
mainfrom
feat/family-reach-backport
Nov 3, 2026
Merged

HaoZeke merged 2 commits into
mainfrom
feat/family-reach-backport

Conversation

@HaoZeke

@HaoZeke HaoZeke commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Reproducer

eb-stack stack sbom \
  --lock tests/backport_fixtures/stack.lock.json \
  --out /tmp/backport.cdx.json \
  --backport-list /tmp/backport.md \
  --osv-cache tests/backport_fixtures/osv \
  --reachability tests/backport_fixtures/reach.txt

The fixture stack has seven modules, and two of them justify a backport:

sbom=/tmp/backport.cdx.json components=7 recipes_read=7 recipes_unreadable=0 with_artifact_facts=0
backport=2 recorded=3 stack=7 out=/tmp/backport.md

What and why

U6, LLVM-family site-layer warning.
A foss universe can't see lfoss, lompi, lfbf or llvm-compilers.
The baseline drops an easystack entry on one of those toolchains.
The stack diff then marks the foss build of the same name as added, so it ends up in the site layer by mistake.
stack solve now prints a warning on standard error for that case and still exits 0.
The fix is one lock per toolchain family, or --baseline-also-toolchain for that family.
A version bump, where the foss solve didn't add the name, stays quiet.

U8, reach classes and the backport list.
The component-reach (schema 2) and rebuild-set (schema 5) outputs now record a reach class.
The classes are cross-user, network-facing, local-only, not-reachable and unclassified.
Only cross-user and network-facing justify a backport.
stack sbom --backport-list writes those modules for a stack lock.
It uses Open Source Vulnerabilities (OSV) ranges and the reverse closure of the software bill of materials (SBOM).
The other classes stay in the recorded section of the list.
The eb_stack_sbom tool takes the same arguments, and the tool count stays at 50.

The package-identity test rejects a production comment that names an easyconfig.
The comment on the warning named one, and now it doesn't.
The integration job now runs --test family_site_layer.

Before and after

Each command runs from the repository root.

stack solve

cat > /tmp/lfoss.yml <<'YAML'
easyconfigs:
  - GROMACS-2025.0-lfoss-2025b.eb
YAML
eb-stack stack solve \
  --easyconfigs fixtures/gromacs_2025_to_next/easyconfigs \
  --toolchain foss/2025b --root GROMACS \
  --baseline-easystack /tmp/lfoss.yml \
  --lock-out /tmp/lock.json \
  --stack-diff-out /tmp/diff.md

Before, the solve exits 0 and prints only the missing entry on standard error:

baseline: 1 easystack entry name no easyconfig in the trees
  GROMACS-2025.0-lfoss-2025b.eb

After, the solve still exits 0 with 5 packages in the lock, and it adds the warning:

baseline: 1 easystack entry name no easyconfig in the trees
  GROMACS-2025.0-lfoss-2025b.eb
warning: GROMACS (GROMACS-2025.0-lfoss-2025b.eb) is the LLVM family (lfoss). A foss solve cannot reach it, so the site layer includes GROMACS (fixtures/gromacs_2025_to_next/easyconfigs/foss-2025b/GROMACS-2025.0-foss-2025b.eb). Keep one lock per toolchain family: solve lfoss on its own, or pass --baseline-also-toolchain lfoss/2025b.

component-reach

eb-stack component-reach \
  --component curl \
  --version '>=8,<9' \
  --generation 2026.1 \
  --easyconfigs tests/component_reach_recorded \
  --generated-at 2026-10-24T00:00:00Z \
  --out-dir /tmp/component-curl

The two runs wrote to separate /tmp/before and /tmp/after directories.
Before:

component=curl version=>=8,<9 generation=2026.1 carriers=2 changed=1 must-rebuild=1 review=0 no-rebuild=1 out=/tmp/before/component-curl

After:

component=curl version=>=8,<9 generation=2026.1 reach=network-facing carriers=2 changed=1 must-rebuild=1 review=0 no-rebuild=1 out=/tmp/after/component-curl

rebuild-set

eb-stack rebuild-set \
  --easyconfigs tests/sbom_recorded/rebuild \
  --installed tests/sbom_recorded/rebuild/installed.txt \
  --installed-label none \
  --generated-at 2026-10-24T00:00:00Z \
  --changed tests/sbom_recorded/rebuild/changed.txt \
  --easystack tests/sbom_recorded/rebuild/easystacks/eessi-2026.06-eb-5.4.0-2026.1.yml \
  --out-dir /tmp/rebuild

The summary line is the same before and after.
Before, rebuild-set.md ended at the paragraph that starts "That comparison can go wrong".
After, that paragraph is followed by:

Reach class of each patched easyconfig. `cross-user` and `network-facing` justify a backport. `local-only` and `not-reachable` do not. `unclassified` is not a judgment.

- `git-2.52.0-GCCcore-15.2.0.eb`: network-facing

stack sbom --backport-list

Before, the flag didn't exist.
After, the reproducer above exits 0.
The list has notebook/7.4.0-GCCcore-15.2.0 (cross-user, blast radius 1, fixed in 7.5.0).
It also has cURL/8.17.0-GCCcore-15.2.0 (network-facing, blast radius 2 because git depends on cURL, fixed in 8.18.0).
The recorded section has zlib as local-only, WinOnly as not-reachable and Mystery as unclassified.

Tests

Each check below passes on the tip.
Tests, fmt, clippy and the llms check also ran on the U6 commit.
Sphinx ran on the tip only.

Check Outcome
cargo test --locked --all-targets 1461 passed, 0 failed, 10 ignored
scripts/gen-llms-txt.py --check exit 0
cargo clippy --locked --all-targets -- -D warnings exit 0
sphinx-build -W build succeeded.
cargo fmt --check exit 0

Ten ignored tests need the pinned EasyBuild trees from scripts/fetch-upstream-trees.sh.
Four test files cover the new behavior: tests/family_site_layer.rs, tests/backport_list.rs, tests/component_reach.rs and tests/rebuild_set.rs.
CI pending.

The backport tests use the cached advisories in tests/backport_fixtures/osv, and none of them call the live OSV API.

Repro

cargo fmt --check
cargo clippy --locked --all-targets -- -D warnings
cargo test --locked --all-targets
cargo test --locked --test family_site_layer --test backport_list
uvx prek run -a
pixi run -e docs docbld
scripts/check-doc-links.sh docs/build
python3 scripts/gen-llms-txt.py --check

Note

Medium Risk
Changes stack solve stderr behavior, bumps JSON schema versions for rebuild-set and component-reach, and adds advisory-driven backport classification that operators may rely on for security decisions.

Overview
Adds security reach classification and a backport list path, plus a foss solve guardrail when LLVM-family packages look like mistaken site-layer adds.

stack solve now emits a stderr warning (still exit 0) when a foss baseline easystack names an LLVM-family recipe (lfoss, lompi, lfbf, llvm-compilers) and the stack diff marks that package added—because the foss universe cannot see that family. Docs steer operators toward one lock per toolchain family or --baseline-also-toolchain.

component-reach (schema 2) and rebuild-set (schema 5) record reach classes (cross-user, network-facing, local-only, not-reachable, unclassified); summaries and markdown now expose reach= / per-file reach sections. Only cross-user and `network-facing justify a backport.

stack sbom --backport-list (and eb_stack_sbom) match locked modules to OSV via --cve or --osv-cache, optional --reachability, compute blast radius from SBOM reverse closure, and write markdown plus sibling JSON. CI adds backport_list and family_site_layer integration tests with fixtures under tests/backport_fixtures/.

Reviewed by Cursor Bugbot for commit f081fe6. Bugbot is set up for automated code reviews on this repo. Configure here.

…ite layer

A foss universe cannot see lfoss, lompi, lfbf, or llvm-compilers.
The baseline drops an easystack entry on one of those toolchains.
The stack diff then classifies the foss build of the same name as added.
The solve prints that on standard error and still exits 0.
Keep one lock per toolchain family, or pass `--baseline-also-toolchain` for that family.
The `component-reach` (schema 2) and `rebuild-set` (schema 5) outputs record cross-user, network-facing, local-only, not-reachable, or unclassified.
The `stack sbom --backport-list` option writes the modules whose class justifies a backport, using Open Source Vulnerabilities (OSV) ranges and the software bill of materials (SBOM) reverse closure.
Modules classed local-only, not-reachable, or unclassified stay in the recorded section.
@HaoZeke
HaoZeke merged commit 9f492e4 into main Nov 3, 2026
19 checks passed
@HaoZeke
HaoZeke deleted the feat/family-reach-backport branch November 4, 2026 09:44
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.

1 participant