Inspect writes a work queue, not a recipe¶
Inspect writes a work queue. It does not write a recipe.¶
Inspect writes a work queue. It does not write a recipe.¶
A residual is named before any rewrite. The plan is the list of those names.¶
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 page
eb-stack ports a recipe and bumps one: the claim ladder. This page is the first two rungs’ artifacts.
The link 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 that lacks EasyBuild and a robot tree.
Its output is the remainder of this page.
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
The locks/ directory is missing.
The easyconfigs/ directory is missing.
Resolvo is not called by Inspection.
The lock and easyconfigs directories are still missing.
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 are listed below.
These names belong to the plan, not to a conda-forge recipe.yaml.
Inspecting a conda-forge recipe does not emit Spack residuals. The
origin on each residual is the format that was passed in.
A rattler-build recipe uses build.number and script; outputs
is for split packages.
origin records conda-forge, spack, pypi, cran, cargo, or easy-build.
package records normalised name, version, homepage, license.
sources records URLs, filenames, SHA-256, VCS identity.
dependencies records names, constraints, roles, conditions, provenance.
build records the planned EasyBuild toolchain, easyblock, patches, and typed easyconfig parameters.
profiles records independently installable products.
outputs records requested profile and stack-policy pairs.
residuals records the work queue.
Provenance on a source or a dependency is exact, derived, or ambiguous, plus a span.
Written paths are identities such as conda-forge/recipe.yaml, spack/package.py.
Written paths are not checkout locations.
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¶
The cards that follow are examples from both formats rather than one inspect of
the eOn recipe.yaml. A conda-forge inspect of that fixture does
not emit R2 or R4.
Residual¶
id R1 stage parse category missing-checksum severity blocking summary Version bump has no SHA-256 and –source-checksum was not passed.
Residual¶
id R2 stage parse category imperative-patch severity judgment summary Spack def patch(self) applies sed at build time; no patch file is beside the .eb.
Residual¶
id R3 stage parse category split-outputs severity judgment summary rattler-build outputs splits packages; EasyBuild has one prefix per easyconfig.
Residual¶
id R4 stage parse category range severity judgment summary Spack states a lower bound; the robot has not yet picked a pin this generation provides.
Residual¶
id R5 stage parse category multi-source-extract severity judgment summary Extract layout is not a single tarball.
Residual¶
{
"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 for triage.
mechanical: deterministic formatting or metadata.recipe formatcan close some of these.judgment: package-authoring or target evidence is required. A Spackdef 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.
Split outputs, generation pins, and multi-source extract layout remain residual.
That sentence explains 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.
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. No patch file exists 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 defining resolves is package plan.
The current CLI requires --stack-policy. The port listing on eb-stack ports a recipe and bumps one already passes it.
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.
Optional decoration is not allowed.
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.
Another declared requirement may render 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.
The bump solver applies an unconstrained policy for the requested toolchain.
This occurs without --stack-policy.
plan of a foreign recipe does not receive that relaxation.
The flag is required.
A direct dependency may lack 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 happens when that command is run as written?
Which extra file does the command need, and what does a
preferredpin in it control?After a successful plan, which two artifacts must exist before
campaign runstarts?
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. The pin 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.