{
"schema_version": 1,
"toolchain": {"name": "foss", "version": "2025b"},
"generation_label": "foss-2025b",
"packages": [
{
"name": "GROMACS",
"version": "2025.0",
"toolchain": {"name": "foss", "version": "2025b"},
"versionsuffix": null,
"easyconfig_path": "foss-2025b/GROMACS-2025.0-foss-2025b.eb"
}
],
"solver": {
"engine": "resolvo_cdcl_sat",
"engine_version": "resolvo+eb_stack-0.4.0",
"timestamp": "2026-01-01T00:00:00Z",
"criteria": ["-notuptodate"]
}
}schema_version1.toolchain- The policy’s target toolchain (not necessarily every package’s own toolchain field, though in practice they match).
generation_label- Optional human label, e.g.
"foss-2025b"when produced from an easyconfig tree. Library callers may omit it for hand-built universes. packages- One entry per co-selected package, sorted by name. Each
entry is a
LockPackage:name,version, its owntoolchain, optionalversionsuffix, and theeasyconfig_pathit came from. solverengineis always"resolvo_cdcl_sat".engine_versionembeds the crate version.timestampis RFC 3339 in Coordinated Universal Time (UTC), second precision.criteriais the Common Upgradeability Description Format (CUDF) list the solve applied, in order. A policy that sets neithercriterianorprefer_installedrecords["-notuptodate"].prefer_installedrecords["-changed", "-notuptodate"]. An explicitcriterialist is recorded as written. See the policy reference.
{
"toolchain": {"name": "foss", "version": "2025b"},
"generation_label": "foss-2025b",
"candidates": [
{
"name": "GROMACS",
"version": "2025.0",
"toolchain": {"name": "foss", "version": "2025b"},
"versionsuffix": null,
"easyconfig_path": "foss-2025b/GROMACS-2025.0-foss-2025b.eb",
"dependencies": [
{"name": "OpenMPI", "version_req": "5.0.3"}
],
"builddependencies": []
}
]
}dependencies and builddependencies use the same DepReq shape and the
same version_req grammar as policy pins (see
policy reference). This structure is a Rust/library input;
the version-one CLI builds it from repeatable --easyconfigs paths.
Plain text, one easyconfig path per line, trailing newline, no blank lines:
fixtures/gromacs_2025_to_next/easyconfigs/foss-2025b/FFTW-3.3.10-foss-2025b.eb
fixtures/gromacs_2025_to_next/easyconfigs/foss-2025b/OpenBLAS-0.3.27-foss-2025b.eb
fixtures/gromacs_2025_to_next/easyconfigs/foss-2025b/OpenMPI-5.0.3-foss-2025b.eb
fixtures/gromacs_2025_to_next/easyconfigs/foss-2025b/Python-3.12.3-foss-2025b.eb
fixtures/gromacs_2025_to_next/easyconfigs/foss-2025b/GROMACS-2025.0-foss-2025b.eb
Order is a topological sort over the co-selected stack: a dependency’s path
always appears before every co-selected package that needs it (runtime or
build-time). Ties are broken by package name for a deterministic order
independent of input list order or HashMap iteration. An empty lock
produces an empty file.
Markdown, meant to be pasted directly into a pull request:
# Stack diff
Baseline (`foss-2025a`) to solved (`foss-2025b`).
## Summary
- **unchanged**: 2
- **added**: 0
- **baseline not in this layer**: 0
- **version-bumped**: 3
## Packages
### FFTW: unchanged
- Baseline: `3.3.10` (`fixtures/gromacs_2025_to_next/easyconfigs/foss-2025a/FFTW-3.3.10-foss-2025a.eb`)
- Solved: `3.3.10` (`fixtures/gromacs_2025_to_next/easyconfigs/foss-2025b/FFTW-3.3.10-foss-2025b.eb`)
### GROMACS: version-bumped
- Baseline: `2024.1` (`fixtures/gromacs_2025_to_next/easyconfigs/foss-2025a/GROMACS-2024.1-foss-2025a.eb`)
- Solved: `2025.0` (`fixtures/gromacs_2025_to_next/easyconfigs/foss-2025b/GROMACS-2025.0-foss-2025b.eb`)
- Change: `2024.1` to `2025.0`
### OpenBLAS: version-bumped
- Baseline: `0.3.23` (`fixtures/gromacs_2025_to_next/easyconfigs/foss-2025a/OpenBLAS-0.3.23-foss-2025a.eb`)
- Solved: `0.3.27` (`fixtures/gromacs_2025_to_next/easyconfigs/foss-2025b/OpenBLAS-0.3.27-foss-2025b.eb`)
- Change: `0.3.23` to `0.3.27`
### OpenMPI: version-bumped
- Baseline: `4.1.5` (`fixtures/gromacs_2025_to_next/easyconfigs/foss-2025a/OpenMPI-4.1.5-foss-2025a.eb`)
- Solved: `5.0.3` (`fixtures/gromacs_2025_to_next/easyconfigs/foss-2025b/OpenMPI-5.0.3-foss-2025b.eb`)
- Change: `4.1.5` to `5.0.3`
### Python: unchanged
- Baseline: `3.12.3` (`fixtures/gromacs_2025_to_next/easyconfigs/foss-2025a/Python-3.12.3-foss-2025a.eb`)
- Solved: `3.12.3` (`fixtures/gromacs_2025_to_next/easyconfigs/foss-2025b/Python-3.12.3-foss-2025b.eb`)Every package present in either the baseline or the solved lock is
classified once: unchanged (same version both sides), added (solved
only), baseline not in this layer (in the baseline, absent from this
solve; the machine token stays removed), or version-bumped (present
both sides, different version). Sections are sorted by package name.
Documents are built with the official cyclonedx-bom crate (same models as cargo-cyclonedx), serialized as CycloneDX 1.5 JSON.
Opt-in: --sbom-out has no default filename. Omit it and stack solve writes
only the lock (and any requested build list / stack diff). eb-stack stack sbom --lock LOCK
defaults to stack.cdx.json because that command’s sole
purpose is SBOM emission.
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"serialNumber": "urn:uuid:…",
"version": 1,
"metadata": {
"timestamp": "2026-01-01T00:00:00Z",
"tools": [{"vendor": "stackhands", "name": "eb-stack", "version": "resolvo+eb_stack-0.4.0"}],
"component": {
"type": "application",
"name": "easybuild-stack-foss-2025b",
"version": "foss-2025b",
"description": "Planned EasyBuild stack inventory from eb-stack lock (pre-install; not a post-build compliance scan)"
},
"lifecycles": [{"phase": "pre-build"}],
"properties": [
{"name": "eb_stack:document_kind", "value": "planned-sbom-from-lock"},
{"name": "eb_stack:solver_engine", "value": "resolvo_cdcl_sat"},
{"name": "eb_stack:toolchain", "value": "foss-2025b"}
]
},
"components": [
{
"type": "library",
"bom-ref": "pkg:generic/GROMACS@2025.0?toolchain=foss-2025b",
"name": "GROMACS",
"version": "2025.0",
"purl": "pkg:generic/GROMACS@2025.0?toolchain=foss-2025b",
"properties": [
{"name": "easybuild:toolchain", "value": "foss-2025b"},
{"name": "easybuild:easyconfig_path", "value": "foss-2025b/GROMACS-2025.0-foss-2025b.eb"},
{"name": "eb_stack:lifecycle", "value": "pre-install-plan"}
]
}
],
"dependencies": [
{"ref": "pkg:generic/GROMACS@2025.0?toolchain=foss-2025b", "dependsOn": ["pkg:generic/Python@3.12.3?toolchain=foss-2025b", "pkg:generic/OpenBLAS@0.3.27?toolchain=foss-2025b", "pkg:generic/OpenMPI@5.0.3?toolchain=foss-2025b", "pkg:generic/FFTW@3.3.10?toolchain=foss-2025b"]}
]
}Each component’s bom-ref / purl is a synthetic pkg:generic/ reference
(EasyBuild has no dedicated Package URL (purl) type) embedding name, version, and
toolchain. Metadata lifecycles: pre-build and
eb_stack:lifecycle: pre-install-plan mark a planned inventory from the
solver selection, not a scan of installed software.
dependsOn edges:
- From
stack solve --sbom-out,eb-stack stack sbom --lock LOCK, and the Model Context Protocol (MCP) tooleb_stack_sbom: each package’s declared runtimedependencies, intersected with co-selected lock members, read from the easyconfigs the lock names. Checksums and source URLs come from those recipes too. Build-time deps (builddependencies) go on the component propertyeb_stack:buildDependsOn(comma-separated refs), not the runtime graph. A relativeeasyconfig_pathis the recipe beside the lock file when that file exists, and otherwise the same path against the process directory. An unreadable path contributes no edges. The document does not invent all-to-all co-stack cycles.stack sbomandeb_stack_sbomreport how many recipes were read. EmptydependsOnarrays may be omitted by cyclonedx-bom serialization.
One easyconfig path per line, in dependency order. A name EasyBuild
installs twice lists both paths: another toolchain, a system bootstrap
pair, or a build-only tool. --installed leaves a listed module, and
the dependencies that were installed with it, out of this file.
--oldest changes which unpinned version is chosen; it does not change
the file shape. When runtime requirements of
one name at one toolchain have no version that satisfies every one of
them, the command writes no file and exits 1. Standard error names each
requester and the version it resolved. The file is not a place where a
pin is rewritten.
Stdout names a co-installable repeated build as
multiple builds of <name>: <ver-tc>, ... and ends with
order=<path> builds=<N>. When --installed is set, the line before
that is installed=<N>, how many listed modules the walk stopped at.
One line per module, two spaces between the fields, the same layout as
sha256sum:
0123abcd... GROMACS-2025.0-foss-2025b
The digest covers the easyconfig bytes and the hashes of everything that
module needs (eb-stack-input-v1). A recipe that cannot be read still
gets a line. Stdout reports incomplete= as how many of those lines are
not a complete closure.
YAML with one easyconfigs list. A filename with no options is a string.
A filename with --option values is a mapping:
easyconfigs:
- OpenMPI-5.0.7-GCC-14.2.0.eb
- CUDA-12.8.0.eb:
options:
accept-eula-for: CUDAstack order --easystack-out writes the build sequence. stack easystack
writes lock order, which is sorted by package name.
One line per recipe whose name the --base inventory already has, then
a counts line. same is the recipe’s software version. different is
another version of that name. unversioned is a base name with no
recorded version. base on a hit is the comma-separated module versions
that decided the kind, or - when none were recorded. --json prints
hits, same, different, unversioned, recipes, and base_names.
same zlib-1.3.1-GCCcore-15.2.0.eb name=zlib version=1.3.1 base=1.3.1-GCCcore-15.2.0
different FFTW-3.3.11-gompi-2023a.eb name=FFTW version=3.3.11 base=3.3.10-gompi-2023a
unversioned OnlyName-1.2-GCCcore-15.2.0.eb name=OnlyName version=1.2 base=-
same=1 different=1 unversioned=1 recipes=4 base=3
The same build-list lines as the input, regrouped under #### Title ####
headers taken from --from. Commented-out lines stay commented out.
Packages the previous list does not name go under the --unfiled header.
Written by package bump when the bumped recipe needed a dependency bumped
with it. A JSON array, in build order:
[
{
"name": "Boost",
"version": "1.92.0",
"toolchain": "GCCcore/15.2.0",
"source": "easybuild/easyconfigs/b/Boost/Boost-1.90.0-GCCcore-15.2.0.eb",
"required_by": "Boost.Python-NumPy"
}
]source is the recipe the companion was bumped from. build-order.json in
the same bundle lists each companion’s recipe before the root’s.
Written by package bump --fetch-checksums when at least one source was
considered. A JSON array:
[
{
"filename": "app-1.1.tar.gz",
"url": "https://example.invalid/app-1.1.tar.gz",
"sha256": "0123abcd",
"errors": []
}
]url and sha256 are omitted when there is nothing to report. errors
is omitted when the download succeeded. respelled appears when the archive
host served the release under the other spelling of its tag, as
{"from": "1.0.10.tar.gz", "to": "v1.0.10.tar.gz"}; the recipe’s sources
entry then names to. The downloaded bytes are not stored in this file.
--scores is a directory of one <case>.json per case. --json prints
that array. Each object has case, pull_request (a number or null),
source, target, score (EXACT, SEMANTIC, or MATERIAL),
allowance (lines forgiven), stale_allowance, and residual_lines.
A stale entry is {"kind": "absent_from_target", "line": "..."} or
{"kind": "already_emitted", "line": "..."}.
The table form is fixed:
| Case | PR | Score | Allowance | Residual |
|---+---+---+---+---|
--ratchet reads note, cases (case name to allowance count), and
total. total has to equal the sum of cases.
One run writes three files. prs/<N>.toml is the SBOM for pull request N:
the closure of the easystack entries it adds, minus modules already
installed. overlap.toml is the same build sets intersected. overlap.md is
that intersection as a report. Every file starts with the never-merge header.
to_build is one sorted module per line, so the same line in two
prs/<N>.toml files is a module both pull requests would build.
sort | uniq -d on those lines, and a staging merge of the files, are the
overlap report.
| Field | Meaning |
|---|---|
schema_version | 1 |
pr | Pull request number |
head_sha | Head commit the record was computed for |
title | Title at that commit, omitted when unknown |
generated_at | RFC 3339 timestamp of the run |
eessi_version | Stack version, from --eessi-version |
installed_source | Label of the installed list |
easystack_files | Easystack paths that contributed an entry |
to_build | Module names that would be built, sorted, one per line |
already_installed | Installed modules the walk reached and stopped at, sorted |
unresolved | Requirements no easyconfig on the robot path satisfies, and easyblock <class> missing when --easyblocks was passed and no passed tree defines that class |
notes | Present when the run recorded one: a class supplied by an include-easyblocks-from-commit tree, a commit an entry names whose tree was not passed, an include-easyblocks-from-pr, or truncated at GitHub's limit when the changed-file list stopped at GitHub’s 3000-file cap. Omitted when empty |
entries | The easystack lines the pull request adds |
build_scope | changed-easystack when --bot-build-set or --arch-installed closed every head entry. Omitted for the added-lines default |
carried | Head entries in to_build that this pull request did not add. Omitted when empty |
build_sets | One table per architecture when more than one --arch-installed was passed. Omitted for a single installed list, whose set is the top-level to_build |
A module whose easyblock class is missing is left out of to_build. The
dependencies of that recipe stay. overlap.md prints each unresolved item
as - unresolved: and each note as - note:.
Each [[entries]] table has easystack, easyconfig, and, when the entry
carries them, from_commit and eb_version. A module name is
name/version[-toolchain]versionsuffix, with the system toolchain omitted.
--bot-build-set does not change entries. A module that was already in
the easystack and is still not installed is listed in carried and in
to_build. Dependencies of that module that are not installed are in
to_build and are not carried. With one installed list, to_build is
that architecture’s build set, and --merge-order and --impact read it.
With two or more --arch-installed values, the top-level to_build,
already_installed, and unresolved arrays are empty.
installed_source is the architecture labels in flag order, joined by a
comma. Each [[build_sets]] table has arch, to_build (one module per
line), already_installed, unresolved, and carried when any head entry
was not installed. Merge order and impact use those to_build lists, one
architecture at a time. sort | uniq -d across every to_build array in
the file mixes architectures. Read one [[build_sets]] table, or the
matching section of overlap.md.
[[revisions]] is each easystack or easyconfig file the pull request
changes. kind is easystack or easyconfig. base and head are the
file text when the fetch stored it. base_blob and head_blob are the git
blob ids of those bytes. A later merge-order reads this text, so the hunk
check does not need the original JSON. Omitted when the run stored none.
A worked example on a test fixture is in from one easystack line to a build set.
| Field | Meaning |
|---|---|
schema_version | 1 |
generated_at | Same timestamp as the records |
eessi_version | Stack version |
installed_source | Label of the installed list |
prs | Pull request numbers that received a record, ascending |
shared | Module to the pull requests that would build it, for modules more than one would build |
pairs | Per pull request, the other pull requests it shares modules with, and which modules |
arches | Present when build_sets is. One table per architecture, with arch, shared, and pairs. The top-level shared map is empty in that case |
The same shared map as a table, then one section per pull request: how
many modules it builds, which other open pull requests it shares them with,
and any unresolved requirements. Pull requests are written as #N text when
--repo-url is empty, and as links otherwise. The flag defaults to
https://github.com/EESSI/software-layer. The file links on GitHub without adding
an entry to a pull request timeline.
Written only with --merge-order (or --merged, which turns that flag
on). The same never-merge header. Schema 1.
| Field | Meaning |
|---|---|
schema_version | 1 |
generated_at | Same timestamp as the overlap index |
installed_source | Label of the installed list |
merged | --merged numbers, ascending. Empty when the flag is absent |
steps | One step per pull request that is not in merged |
cycles | Strongly connected sets. Each set is sorted, and the sets are sorted |
conflicts | Easystack paths edited by two or more pull requests in a colliding way |
arches | Present when the run passed more than one --arch-installed. One table per architecture, with arch, steps, and cycles. The top-level steps array is empty in that case. conflicts stays on the top-level table |
Each [[steps]] table:
| Field | Meaning |
|---|---|
position | 1-based index in steps |
pr | Pull request number |
after | Direct predecessors in the binding graph, ascending |
builds | Residual to_build once every earlier step has merged, sorted |
saves | Later pull requests, and the modules they no longer build |
reason | Why this pull request was picked |
saves is [[steps.saves]] with pr and modules. A conflict has
easystack, prs, and kind. kind is same-insertion or same-entry.
With --merge-order, overlap.md gains a Merge order table: position,
pull request, build count, saves, and after. With more than one
--arch-installed, that table is repeated, one heading per architecture.
Conflicts follow that table
when any were found. Without the flag the Markdown file is unchanged.
Written only when --impact is passed. The same never-merge header.
Schema 1. This is not the residual after earlier steps of
merge-order.toml. The named pull request’s to_build counts as newly
installed, after the modules of every --merged pull request are
removed. Those pull requests are left out of affected. --impact N
when N is also --merged is an error:
--impact N: already listed in --merged.
| Field | Meaning |
|---|---|
schema_version | 1 |
generated_at | Same timestamp as the overlap index |
impacts | One entry per requested pull request, in ascending number order. Empty when arches is present |
arches | Present when the run passed more than one --arch-installed. One table per architecture, with arch and impacts. Each impact is the same shape as the top-level impacts table |
Each [[impacts]] table has pr and affected. Each [[impacts.affected]]
table:
| Field | Meaning |
|---|---|
pr | Another pull request whose build set or depends-on edge changes |
shrinks | Modules that leave that pull request’s to_build, sorted |
resolves | Modules of a depends-on edge from the named pull request. It asks for the module by name, and the other only reaches it. Both asking is not an edge |
An unknown number is an error before the file is written:
--impact N: no pull request with that number. An unknown --merged
number is the same kind of error. A number that is in the input but
changes no easyconfig and no easystack is
pull request N has no record (no easyconfigs or easystack change).
Neither one creates the output
directory or deletes files already in it. For an easyconfigs run,
resolves lists the modules on a depends-on row from the named pull
request, including a module it patches when another pull request builds
a dependent of that module. shrinks is the shared to_build that
remains after --merged modules are removed. Modules from a
depends-on row into the pull request are excluded from shrinks. A
software-layer resolves list is the modules the named pull request
asks for by easystack entry.
--kind easyconfigs writes schema 2. A software-layer record stays schema
1 and omits the fields below. eessi_version is omitted when it is empty.
| Field | Meaning |
|---|---|
schema_version | 2 |
kind | easyconfigs |
changed_easyconfigs | One entry per .eb path: path, status (added, modified, or deleted), and module when the file parses |
closure | Every module the changed easyconfigs reach, sorted, including the pull request’s own modules |
new_modules | Closure modules the base tree does not provide |
reverse_dependents | Base-tree modules that depend on a modified or deleted file of this pull request. Each entry has module, depth, and class. Depth 1 depends on the file itself. class is must-rebuild, review, or no-rebuild, the same three classes as rebuild-set.json. A bundle whose exts_list names the patched package is must-rebuild. A runtime dependency is no-rebuild. A dependent reached only through builddependencies is review. A toolchain edge is not listed. overlap.md prints the module, the depth, and the class |
to_build | Closure minus --installed when that flag is set; otherwise the same modules as new_modules. A modified easyconfig, and every reverse dependent classed must-rebuild, is included even when that module is installed or already in the base tree. review and no-rebuild stay out of to_build and stay on reverse_dependents. A deleted easyconfig is not built. Merge order and --impact schedule this list |
notes | truncated at GitHub's limit when --open stopped at GitHub’s 3000-file cap. The changed files are the ones GitHub returned. Omitted when empty |
The other pull requests in the same run are on the robot path while
resolving, between the base tree and this pull request’s own files.
new_modules is still compared to the base tree alone.
--filter-deps is applied to a copy of the base tree before the reverse
walk. A dependency it drops is not a reverse dependent and does not create
a depends-on row.
overlap.toml for this kind is schema 2 and adds rows. There are 4
kinds:
kind | When it is written |
|---|---|
same-file | 2 pull requests change one path. path is that path. prs lists the numbers |
same-module | 2 pull requests add easyconfigs for one module. module is that module |
shared-new-module | One module is in 2 pull requests’ new_modules |
depends-on | Pull request to reaches a module pull request from adds, to does not also add it, and the module is in from’s new_modules. A file from adds for a module the base tree already provides is not a dependency. The same row is written when from modifies or deletes a module and to builds a dependent of it: the module is in to’s closure, or to’s to_build contains one of from’s reverse dependents, and to does not itself change that module. modules lists the added or patched modules. merge-order.toml orders from before to |
A software-layer index stays schema 1 and omits rows.
One run writes rebuild-set.json, rebuild-set.md, and one easystack per
EasyBuild version under rebuilds/. The date in the easystack filename is
the YYYYMMDD taken from generated_at. The version is the newest
EasyBuild version among the easystacks whose closure contains that
must-rebuild module, read from -eb- in the easystack filename. When no
filename carried a version, the file is unknown.
| Field | Meaning |
|---|---|
schema_version | 5 |
generated_at | RFC 3339 timestamp of the run |
installed_source | Label of the installed list |
prefix | Installed prefix passed to --prefix. Omitted when the flag was not passed |
symbols_checked | true when --check-static ran |
easystacks | Easystack paths that were read, sorted |
changed | Patched easyconfig filenames, sorted |
reach | Reach class of each filename in changed, including missing ones. cross-user and network-facing justify a backport. local-only and not-reachable do not. unclassified means neither the built-in names nor --reachability names the package |
missing | Changed filenames that are not in the deployed graph |
unresolved | Requirements no easyconfig satisfies, and easystack entries the robot path does not have |
notes | A missing easyblock class, a from-commit whose tree was not given, or an install directory that was not found. Empty when there is nothing to say |
counts | Row counts, with keys no-rebuild, review, and must-rebuild |
rows | Installed reverse dependents. A review dependent is one row. The other classes stay one row per edge |
Each row has changed, dependent, dependent_easyconfig, edge_kind
(changed, runtime, builddependency, or extension), class
(no-rebuild, review, or must-rebuild), reason, and eb_version
when an easystack filename carried one. On a review row, changed is the
patched filenames joined by , ~. After ~--check-static, a hit keeps only
the filenames whose exports were defined.
rebuild-set.md is the same rows as a table, the counts line, the missing filenames, and each note as - note:.
It ends with the reach class of each patched easyconfig.
Written when --findings is passed.
| Field | Meaning |
|---|---|
schema_version | 2 |
generated_at | Same as rebuild-set.json |
installed_source | Same as rebuild-set.json |
rows | Rows in the export |
ids | Unique advisory ids in the export |
modules | Installed modules with a finding |
totals | One entry per severity, Critical first: ids and modules at exactly that severity, modules_highest whose worst finding is that severity, modules_at_or_above, and the unions in_place_at_or_above and bump_at_or_above |
ranked | One entry per installed module with a finding, by value_per_rebuild, then score, then name |
backport_modules | How many ranked modules have backport true |
reachability_file | Path of --reachability. Omitted when the flag was not passed |
not_installed | Modules the export names that are not installed |
not_in_graph | Installed modules with a finding whose easyconfig is not on the robot path. They have no closure |
A ranked entry has module, easyconfig, highest, counts (unique ids
per severity: critical, high, medium, low, negligible, other),
findings, fixed, not_fixed, unknown_fix, packages, score,
value_per_rebuild, reach, backport, python_patch_in_place,
fixed_in, and closure. reach is cross-user, network-facing,
local-only, not-reachable, or unclassified. backport is true only
for the first two. python_patch_in_place is true when the module is the
Python interpreter and every named fix stays on the installed minor.
fixed_in lists the fix versions the export names. The closure has
must_rebuild (the module and bundles that ship it), review, runtime,
in_place (must_rebuild + review), bump (the module plus every
dependent), dependents, and in_place_dependents. An id listed twice
for one module counts once, at its worst severity.
findings.md is the totals table and the ranked table, with a Reach
column. findings.csv is one line per ranked module with the same counts,
packages and dependents joined by ;, then reach, backport,
python_patch_in_place, and fixed_in.
Written when --findings is passed. The backport section lists modules
whose reach class is cross-user or network-facing, with the in-place
and bump costs. local-only and not-reachable are a separate table.
unclassified modules are listed and are not a judgment about the
finding. A Python interpreter whose fix stays on the same minor is named
under Python patch releases: the in-place column is the cost of keeping
the module path, and the bump column is the cost of renaming the
easyconfig to that patch version. The built-in name table is in the file.
A site file’s names are a second table. After that, OS errata lists
backport modules that --filter-deps drops: they get no scaffold, and
backport on the JSON stays the reach class. Patch scaffolds follow,
one heading per <Name>-<version>_<ID>.patch, with the patches
assignment, the checksum line, the fixed version, and any fix commit
URL. A cURL finding adds the --filter-deps=cURL note.
Written when --findings is passed. One row per advisory id, package, and
version. The closure is the rebuild set above, and the reach class is the
one in findings.json. This file does not walk the graph again.
| Field | Meaning |
|---|---|
schema_version | 1 |
generated_at | Same as rebuild-set.json |
installed_source | Same as rebuild-set.json |
stack_modules | Installed modules. The denominator of every fraction |
rows | Rows in the export |
findings | Rows in ranked |
ranked | One entry per finding, by route (in-place, bump, defer, unknown), then severity, then the bump closure size, then id |
rollup | Union of each strategy: bump_modules and bump_fraction, in_place_modules and in_place_fraction, recommended_modules and recommended_fraction, over stack_modules, plus static_unknown_modules |
not_installed | Modules the export names that are not installed |
not_in_graph | Installed modules with a finding whose easyconfig is not on the robot path |
notes | An unreadable recipe, or a components entry whose version was not a literal |
A ranked entry has vuln_id, package, version, severity, fixed_in,
fix_state, reach, modules, not_in_graph, patch_files
(<Name>-<version>_<ID>.patch), bump, in_place, bundles,
static_linkers, not_static, static_unknown, recommended, and
reason. bump and in_place each have modules, fraction, and
names. bump also has breaks_reproducibility, true when the closure
is known. recommended follows the backport classes: in-place for
cross-user and network-facing, bump when a Python fix leaves the
minor version, and defer for local-only, not-reachable, and
unclassified.
fix-cost.tsv is the same rows, tab-separated, with a header. Lists are
joined by ;. fix-cost.md is the roll-up, the ranked table, and a short
block per finding.
Class must-rebuild only. Entries are sorted by filename. rebuild: true
is set because the module is already installed.
# In-place patches, same filename and version.
# Class must-rebuild only: the patched easyconfig, a bundle that includes it as an extension,
# and, when --check-static was used, a dependent that defines an export of the patched library.
# Generated by `eb-stack rebuild-set`.
easyconfigs:
- Bundle-1.0-GCCcore-15.2.0.eb:
options:
rebuild: trueA worked classification is in what an in-place patch forces a rebuild of.
One run writes component-reach.toml, component-reach.json, and
component-reach.md. The TOML file starts with
# Generated by `eb-stack component-reach`.
| Field | Meaning |
|---|---|
schema_version | 2 |
generated_at | RFC 3339 timestamp of the run |
component | Name that was asked for |
version_req | Exact version, *, or a requirement. Empty when the match is only versions |
version_reqs | Present when more than one requirement was OR-ed. Any one of them matches |
versions | Exact versions from a Common Vulnerabilities and Exposures (CVE) record, present when the list is not empty |
purl | Package URL, when the query was a purl or a resolved CVE |
ecosystem | Ecosystem from the purl or the Open Source Vulnerabilities (OSV) record |
generation | Toolchain generation |
reach | Class of the component name: cross-user, network-facing, local-only, not-reachable, or unclassified |
carriers | Easyconfigs and modules that carry it |
changed | Filenames that contain it, fed to the closure |
closure_counts | Row counts, keys no-rebuild, review, must-rebuild |
closure_rows | Rebuild-set rows for changed |
missing | Changed filenames absent from the closure graph |
notes | A compat-layer hit, or a note the closure recorded |
version_reqs, versions, purl, and ecosystem are omitted when
empty, so a --component run stays the same shape as before.
Each carrier has role (module, dependency, builddependency,
component, extension, crate, wheel, source, patch,
compat-layer, or installed), version, easyconfig (empty for
compat-layer and installed-only rows), module, and installed when a
list was passed.
A --cve run writes the same three filenames with a different document.
kind is cve.
cve is the id.
resolved is the packages the Open Source Vulnerabilities (OSV) record named (ecosystem, name, purl, version_reqs, versions).
results is one component-reach document per package.
notes contains alias fetches that failed.
Schema version stays 2.
Each package document carries its own reach.
A closure row has the rebuild-set fields changed, dependent,
dependent_easyconfig, edge_kind, class, reason, and eb_version
when an easystack filename carried one. With no easystack the synthetic
stack is generation-<generation>.yml and eb_version is absent.
component-reach.md is the carrier table, then the closure table, the counts line, and each note as - note:.
The carriers line names the reach class.
A path ending in .md writes that markdown and a sibling .json.
| Field | Meaning |
|---|---|
schema_version | 1 |
lock | Lock path, as it was passed |
generation | Toolchain label of the lock |
advisories | Advisory ids that were resolved, sorted |
stack_modules | Packages in the lock |
listed | Rows whose reach class justifies a backport |
recorded | Rows left off the list |
rows | One entry per locked module an advisory matched. On-list rows come first |
notes | An advisory names a package the lock lacks, a locked version is outside the range, or a range did not parse |
Each row has module (Name/version-toolchain, plus the versionsuffix when the lock has one), name, version, reach, advisory, fixed_in (upper bounds from < clauses; a <= bound is last-affected and is not a fix), blast_radius, blast (those module labels, sorted), and on_list.
Blast radius counts the module and every locked module that depends on it through a runtime or build edge.
The markdown lists each module in a backport section or a recorded section. The recorded sentence is: these modules stay off the backport list, and a backport is cross-user or network-facing.