A stack lock is not a package lock

Questions

  • What does stack solve take that package plan does not?

  • Which file is safe to paste into a pull request, and which one is the install order?

  • Why can stack sbom --lock not reconstruct dependency edges?

What you will be able to do

  1. Write a whole-stack policy JSON and run stack solve against a fixture tree, naming every artifact the flags emit.

  2. Tell a package-profile stack-policy TOML from a whole-stack policy JSON.

  3. Read a stack.diff.md and say which packages moved, and which file a sequential eb loop should consume instead.

Before this chapter

package plan evaluates one package’s profiles against a robot tree. The annual bump of a site stack poses another question. Given many roots and a single target generation, it asks which jointly consistent set is built. It also asks in what order the set is built and what has changed since the previous year. The command is stack solve.

A policy for many roots

The fixture policy located in fixtures/gromacs_2025_to_next/policies/prefer_newer.json represents the smallest interesting case.

{
  "toolchain": {"name": "foss", "version": "2025b"},
  "roots": ["GROMACS"],
  "root_priority": ["GROMACS"],
  "pins": [],
  "forbid": [],
  "objective": "prefer_newer",
  "require_upgrade": {"name": "GROMACS", "relative_to_baseline": true}
}

Roots and each declared runtime and build requirement are co‑selected. root_priority makes a multi‑root solve stable under list order. Pins use EasyBuild‑shaped requirements (= =0.3.24, = >=1.14=). forbid accepts a package name or a full easyconfig path. require_upgrade with relative_to_baseline fails when the baseline package is absent or no newer jointly feasible candidate exists.

This JSON is not the TOML --stack-policy of Inspect writes a work queue, not a recipe. Package commands accept TOML preferences for a single product. Whole‑stack solve uses this JSON schema. Mixing the files produces a parse error, not a clever override.

Four artifacts from one solve

eb-stack stack solve \
  --easyconfigs fixtures/gromacs_2025_to_next/easyconfigs \
  --policy fixtures/gromacs_2025_to_next/policies/prefer_newer.json \
  --baseline-easyconfigs fixtures/gromacs_2025_to_next/easyconfigs \
  --lock-out stack.lock.json \
  --sbom-out stack.cdx.json \
  --build-list-out build.list \
  --stack-diff-out stack.diff.md

The --easyconfigs option can be used repeatedly. Later paths replace earlier ones when name, version, toolchain, and versionsuffix match. A site‑only package is then added to the universe. --stack-diff-out mandates the presence of --baseline-easyconfigs. Omitting the baseline triggers an error rather than a silent skip. stack.lock.json conforms to schema 1. toolchain represents the policy’s target. packages denotes the co‑selected set, ordered by name, each entry containing its own toolchain, an optional versionsuffix, and an easyconfig_path. solver.engine is always set to resolvo_cdcl_sat. build.list contains a single easyconfig path per line, arranged topologically over the co‑selected stack with ties resolved by package name. An empty lock yields an empty file. A sequential eb loop, a Jenkins step, or xargs -n1 reads this file. The file is neither markdown nor an SBOM. stack.diff.md is the pull‑request body. Each co‑selected package is classified as unchanged, added, removed, or version-bumped relative to the baseline:

# Stack diff

Baseline (`foss-2025a`) -> solved (`foss-2025b`).

## Summary

- **unchanged**: 1
- **added**: 0
- **removed**: 0
- **version-bumped**: 3

The file stack.cdx.json generated during solve retains the candidate graph. Dependency edges. The alternative SBOM command does not include them.

eb-stack stack sbom --lock stack.lock.json --out stack.cdx.json

A lock‑only SBOM cannot rebuild edges and does not create them. Apply --sbom-out to stack solve while the graph remains in memory.

EB-Stack-1 — Which file answers

A site ran the fixture solve above. Four questions arrive the next morning. Name the file that answers each one, or say the solve was missing a flag.

  1. “In which order do we eb these?”

  2. “What do we tell the pull request?”

  3. “Which OpenMPI did Resolvo actually pick?”

  4. “Give us a CycloneDX with dependency edges, from the lock we already committed, without re-solving.”

Solution
  1. build.list. Topological, one path per line.

  2. stack.diff.md. That is why it is markdown.

  3. stack.lock.json, the packages entry whose name is OpenMPI.

  4. That file cannot be produced from the lock alone. stack sbom --lock will write a document without edges. The solve needed --sbom-out while the graph was available.

What to remember

  • stack solve is the multi-root annual-bump solver. Its policy is JSON. The package command’s --stack-policy is TOML. They are not interchangeable.

  • build.list is the install order. stack.diff.md is the pull request. stack.lock.json is the Resolvo assignment.

  • A lock-only SBOM has no dependency edges. Edges are written only when --sbom-out is passed to stack solve.