Inspect writes a work queue, not a recipe¶
Questions
What two files does
package inspectwrite, and which claim do they establish?When is a leftover a residual, and when is it only a parser note?
Why does
package planexit without--stack-policy?
What you will be able to do
Run
package inspecton a foreign recipe and name every section ofpackage.plan.jsonthat is evidence, and every residual that is work.Tell a
mechanicalresidual from ajudgmentresidual from ablockingone, and say which stage each belongs to.Write the
package plancommand that turns that inspect tree into a lock and a.eb, including the stack policy the CLI requires.
Before this chapter
eb-stack ports a recipe and bumps one: the claim ladder. This chapter is the first two rungs’ artifacts.
eb-stack ports a recipe and bumps one placed the conda-forge recipe beside the .eb and halted at the residual queue.
package inspect is the command executable from a fresh clone lacking EasyBuild and a robot tree.
Its output constitutes the remainder of this chapter.
The fixture comprises the rattler-build v1 recipe for eOn, captured from the conda-forge feedstock:
eb-stack package inspect \
--source fixtures/foreign_ingest/conda_eon/recipe.yaml \
--format conda-forge \
--toolchain-name foss \
--toolchain-version 2026.1 \
--package-config examples/packages/common.toml \
--package-config examples/packages/eon.toml \
--out-dir /tmp/eon-inspect
Two files come out, and nothing else:
/tmp/eon-inspect/package.plan.json
/tmp/eon-inspect/package.sbom.cdx.json
There is no locks/ directory and no easyconfigs/.
Inspection does not call Resolvo.
It establishes no rung of the ladder.
The plan is a manifest with provenance¶
package.plan.json has schema_version 1.
Unknown fields are rejected.
The sections that matter on a first read:
Field |
What it records |
|---|---|
|
|
|
normalised name, version, homepage, license |
|
URLs, filenames, SHA-256, VCS identity |
|
names, constraints, roles, conditions, provenance |
|
toolchain, easyblock, patches, typed easyconfig parameters |
|
independently installable products |
|
requested profile and stack-policy pairs |
|
the work queue |
Provenance on a source or a dependency is exact, derived, or ambiguous, plus a span.
Written paths are identities (conda-forge/recipe.yaml, spack/package.py), not checkout locations, so two hosts produce the same JSON from the same input.
A conda build.number is a parser note.
A compiler or cross-python template that the EasyBuild toolchain already represents is also a parser note.
Notes are diagnostics.
Planning never greps them for the word “residual”.
Anything that needs a human or a later stage is a typed residual instead.
Residuals are the work queue¶
Each residual includes id, stage, category, severity, summary, and may also contain optional evidence and provenance.
{
"id": "foreign:imperative-patch:0",
"stage": "parse",
"category": "imperative-patch",
"severity": "judgment",
"summary": "imperative patch method requires EasyBuild translation",
"provenance": {
"span": {"path": "package.py", "start_line": 42}
}
}
Severity has three values and they constitute the triage.
mechanical: deterministic formatting or metadata.recipe formatcan close some of these.judgment: package-authoring or target evidence is required.A Spack
def patch(self):method that is not a file is this.blocking: the next pipeline stage cannot make a valid claim.A missing SHA-256 on a version bump without
--source-checksumis this.
Stage is parse, normalization, resolution, emission, build, or verification.
An inspect residual is almost always parse.
A bump that changes the package version and does not pass --source-checksum adds a residual.
The foreign-ingest README is explicit about the limit; these fixtures drive parser regression.
They do not claim parity with a hand-authored EasyBuild pull request.
Product flags, generation pins, and multi-source extract layout remain residual.
That sentence is why inspect exists as a command of its own.
The work queue is visible before Resolvo spends a robot tree on a recipe that still needs judgment.
The planned SBOM, package.sbom.cdx.json, is CycloneDX 1.5 derived from the same plan.
It is a pre-build inventory.
It carries no generation timestamp, so two inspect runs of the same input hash the same.
It does not establish builds or binary-verified.
EB-Residual-1 — Note, residual, or claim
Four inspect outputs. For each, say whether it is a parser note, a typed residual (and which severity), or a claim.
A conda
build.numberof 3.A Spack
def patch(self):that applies a sed at build time.A source URL with a SHA-256 taken from the feedstock.
package.sbom.cdx.jsonlisting the same source.
Solution
A parser note. The EasyBuild toolchain represents the rebuild counter; nothing in the next stage has to act on it.
A
judgmentresidual, categoryimperative-patch, stageparse. There is no patch file to copy beside the emitted.eb. Someone has to write one, or drop the change.Evidence in
sources. Not a residual. Provenance isexact.The planned SBOM. Not a claim. The same inventory in a different schema.
resolvesstill has not been established.
Plan is inspect plus Resolvo, and it needs a policy¶
The command that defines resolves is package plan.
The current CLI needs --stack-policy.
eb-stack ports a recipe and bumps one’s example left it out.
The live form from the tutorial is:
eb-stack package plan \
--source fixtures/foreign_ingest/conda_eon/recipe.yaml \
--format conda-forge \
--toolchain-name foss \
--toolchain-version 2026.1 \
--package-config examples/packages/common.toml \
--package-config examples/packages/eon.toml \
--easyconfigs "$ROBOT" \
--easyconfigs /tmp/eb-stack/overlay \
--stack-policy examples/stacks/eon-foss-2026.1.toml \
--out-dir "$BUNDLE"
The generated tree is now:
package.plan.json
package.sbom.cdx.json
locks/default.lock.json
easyconfigs/e/eOn/eOn-2.16.0-foss-2026.1.eb
The lock corresponds to the Resolvo result.
Direct dependencies specify selected versions, toolchains, versionsuffixes, source easyconfig paths, build/runtime roles, pin outcomes, and candidate exclusions.
The stack-policy TOML is mandatory, not optional decoration.
The eOn example records the Eigen 5 identity required by the safemath core-guard patch as a preferred pin with the complete EasyBuild toolchain and versionsuffix.
Resolvo attempts that candidate first.
If another declared requirement renders it infeasible, the solver selects a compatible alternative and sets fallback = true with a reason.
locked is the mode that must instead fail.
package bump of an existing .eb produces the same bundle.
Without --stack-policy, the bump solver applies an unconstrained policy for the requested toolchain.
plan of a foreign recipe does not receive that relaxation; the flag is required.
When a direct dependency lacks a robot candidate, --package-catalog and --package-sources close the gap without inventing versions.
A closed plan writes companions under packages/, a shared easyconfigs/ overlay, build-order.json, and closure.sbom.cdx.json.
Robot candidates continue to take precedence over catalog entries.
EB-Plan-1 — What is missing from this command
A colleague pastes eb-stack ports a recipe and bumps one’s plan sample:
eb-stack package plan \
--source recipe.yaml \
--format conda-forge \
--toolchain-name foss \
--toolchain-version 2024a \
--easyconfigs /path/to/easybuild-easyconfigs \
--out-dir /tmp/new-package
What action does the current CLI perform with this?
Which file is added, and which aspect does a
preferredpin in it control?After a successful plan, which two paths must exist before
campaign runwill start?
Solution
It exits with an error.
--stack-policyis required onpackage plan.A stack-policy TOML, for example
examples/stacks/eon-foss-2026.1.toml. Apreferredpin names the candidate Resolvo tries first and recordsfallback = trueif it cannot keep it. It is not a lock until the solve writeslocks/default.lock.json.At least one
locks/<profile>.lock.jsonand one emitted.eb. Inspect’s two JSON files are not enough. The campaign reference says so: a campaign requires at least one lock and recipe.
What to remember
package inspectwritespackage.plan.jsonand a planned CycloneDX SBOM. It establishes no claim.Residuals are the work queue. Notes are not. Severity is
mechanical,judgment, orblocking.package planrequires--stack-policy. The lock and the emitted.ebare what establishresolves.