Skip to content

Latest commit

 

History

History
766 lines (632 loc) · 36.5 KB

formats.org

File metadata and controls

766 lines (632 loc) · 36.5 KB

Output formats

1 Stack lock (stack.lock.json)

{
  "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_version
1.
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 own toolchain, optional versionsuffix, and the easyconfig_path it came from.
solver
engine is always "resolvo_cdcl_sat". engine_version embeds the crate version. timestamp is RFC 3339 in Coordinated Universal Time (UTC), second precision. criteria is the Common Upgradeability Description Format (CUDF) list the solve applied, in order. A policy that sets neither criteria nor prefer_installed records ["-notuptodate"]. prefer_installed records ["-changed", "-notuptodate"]. An explicit criteria list is recorded as written. See the policy reference.

2 Internal candidate universe

{
  "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.

3 Build list (--build-list-out)

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.

4 Stack diff (--stack-diff-out)

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.

5 Planned Software Bill of Materials (SBOM) (--sbom-out, CycloneDX 1.5)

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) tool eb_stack_sbom: each package’s declared runtime dependencies, 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 property eb_stack:buildDependsOn (comma-separated refs), not the runtime graph. A relative easyconfig_path is 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 sbom and eb_stack_sbom report how many recipes were read. Empty dependsOn arrays may be omitted by cyclonedx-bom serialization.

6 Build order (stack order --out)

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.

7 Input hashes (--hashes-out)

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.

8 Easystack (stack easystack, --easystack-out)

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: CUDA

stack order --easystack-out writes the build sequence. stack easystack writes lock order, which is sorted by package name.

9 Site-layer shadow (stack shadow)

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

10 Jenkins sections (stack sections)

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.

11 Companion bumps (companions.json)

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.

12 Source fetch (sources.fetch.json)

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.

13 Reproduction scores (repro summary)

--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.

14 Pull-request build set (eb-stack pr-overlap)

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.

14.1 prs/<N>.toml

FieldMeaning
schema_version1
prPull request number
head_shaHead commit the record was computed for
titleTitle at that commit, omitted when unknown
generated_atRFC 3339 timestamp of the run
eessi_versionStack version, from --eessi-version
installed_sourceLabel of the installed list
easystack_filesEasystack paths that contributed an entry
to_buildModule names that would be built, sorted, one per line
already_installedInstalled modules the walk reached and stopped at, sorted
unresolvedRequirements no easyconfig on the robot path satisfies, and easyblock <class> missing when --easyblocks was passed and no passed tree defines that class
notesPresent 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
entriesThe easystack lines the pull request adds
build_scopechanged-easystack when --bot-build-set or --arch-installed closed every head entry. Omitted for the added-lines default
carriedHead entries in to_build that this pull request did not add. Omitted when empty
build_setsOne 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.

14.2 overlap.toml

FieldMeaning
schema_version1
generated_atSame timestamp as the records
eessi_versionStack version
installed_sourceLabel of the installed list
prsPull request numbers that received a record, ascending
sharedModule to the pull requests that would build it, for modules more than one would build
pairsPer pull request, the other pull requests it shares modules with, and which modules
archesPresent when build_sets is. One table per architecture, with arch, shared, and pairs. The top-level shared map is empty in that case

14.3 overlap.md

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.

14.4 merge-order.toml

Written only with --merge-order (or --merged, which turns that flag on). The same never-merge header. Schema 1.

FieldMeaning
schema_version1
generated_atSame timestamp as the overlap index
installed_sourceLabel of the installed list
merged--merged numbers, ascending. Empty when the flag is absent
stepsOne step per pull request that is not in merged
cyclesStrongly connected sets. Each set is sorted, and the sets are sorted
conflictsEasystack paths edited by two or more pull requests in a colliding way
archesPresent 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:

FieldMeaning
position1-based index in steps
prPull request number
afterDirect predecessors in the binding graph, ascending
buildsResidual to_build once every earlier step has merged, sorted
savesLater pull requests, and the modules they no longer build
reasonWhy 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.

14.5 merge-impact.toml

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.

FieldMeaning
schema_version1
generated_atSame timestamp as the overlap index
impactsOne entry per requested pull request, in ascending number order. Empty when arches is present
archesPresent 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:

FieldMeaning
prAnother pull request whose build set or depends-on edge changes
shrinksModules that leave that pull request’s to_build, sorted
resolvesModules 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.

14.6 Easyconfigs records (schema 2)

--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.

FieldMeaning
schema_version2
kindeasyconfigs
changed_easyconfigsOne entry per .eb path: path, status (added, modified, or deleted), and module when the file parses
closureEvery module the changed easyconfigs reach, sorted, including the pull request’s own modules
new_modulesClosure modules the base tree does not provide
reverse_dependentsBase-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_buildClosure 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
notestruncated 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:

kindWhen it is written
same-file2 pull requests change one path. path is that path. prs lists the numbers
same-module2 pull requests add easyconfigs for one module. module is that module
shared-new-moduleOne module is in 2 pull requests’ new_modules
depends-onPull 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.

15 Rebuild set (eb-stack rebuild-set)

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.

15.1 rebuild-set.json

FieldMeaning
schema_version5
generated_atRFC 3339 timestamp of the run
installed_sourceLabel of the installed list
prefixInstalled prefix passed to --prefix. Omitted when the flag was not passed
symbols_checkedtrue when --check-static ran
easystacksEasystack paths that were read, sorted
changedPatched easyconfig filenames, sorted
reachReach 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
missingChanged filenames that are not in the deployed graph
unresolvedRequirements no easyconfig satisfies, and easystack entries the robot path does not have
notesA 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
countsRow counts, with keys no-rebuild, review, and must-rebuild
rowsInstalled 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.

15.2 findings.json, findings.md, findings.csv

Written when --findings is passed.

FieldMeaning
schema_version2
generated_atSame as rebuild-set.json
installed_sourceSame as rebuild-set.json
rowsRows in the export
idsUnique advisory ids in the export
modulesInstalled modules with a finding
totalsOne 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
rankedOne entry per installed module with a finding, by value_per_rebuild, then score, then name
backport_modulesHow many ranked modules have backport true
reachability_filePath of --reachability. Omitted when the flag was not passed
not_installedModules the export names that are not installed
not_in_graphInstalled 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.

15.3 backport.md

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.

15.4 fix-cost.json, fix-cost.tsv, fix-cost.md

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.

FieldMeaning
schema_version1
generated_atSame as rebuild-set.json
installed_sourceSame as rebuild-set.json
stack_modulesInstalled modules. The denominator of every fraction
rowsRows in the export
findingsRows in ranked
rankedOne entry per finding, by route (in-place, bump, defer, unknown), then severity, then the bump closure size, then id
rollupUnion 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_installedModules the export names that are not installed
not_in_graphInstalled modules with a finding whose easyconfig is not on the robot path
notesAn 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.

15.5 rebuilds/<YYYYMMDD>-eb-<version>-cve-patches.yml

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: true

A worked classification is in what an in-place patch forces a rebuild of.

16 Component reach (eb-stack component-reach)

One run writes component-reach.toml, component-reach.json, and component-reach.md. The TOML file starts with # Generated by `eb-stack component-reach`.

FieldMeaning
schema_version2
generated_atRFC 3339 timestamp of the run
componentName that was asked for
version_reqExact version, *, or a requirement. Empty when the match is only versions
version_reqsPresent when more than one requirement was OR-ed. Any one of them matches
versionsExact versions from a Common Vulnerabilities and Exposures (CVE) record, present when the list is not empty
purlPackage URL, when the query was a purl or a resolved CVE
ecosystemEcosystem from the purl or the Open Source Vulnerabilities (OSV) record
generationToolchain generation
reachClass of the component name: cross-user, network-facing, local-only, not-reachable, or unclassified
carriersEasyconfigs and modules that carry it
changedFilenames that contain it, fed to the closure
closure_countsRow counts, keys no-rebuild, review, must-rebuild
closure_rowsRebuild-set rows for changed
missingChanged filenames absent from the closure graph
notesA 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.

17 Backport list (eb-stack stack sbom --backport-list)

A path ending in .md writes that markdown and a sibling .json.

FieldMeaning
schema_version1
lockLock path, as it was passed
generationToolchain label of the lock
advisoriesAdvisory ids that were resolved, sorted
stack_modulesPackages in the lock
listedRows whose reach class justifies a backport
recordedRows left off the list
rowsOne entry per locked module an advisory matched. On-list rows come first
notesAn 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.