Skip to content

Make the env attribute on a block mean something - #36

Merged
tamnd merged 1 commit into
mainfrom
env-tiers
Sep 5, 2026
Merged

tamnd merged 1 commit into
mainfrom
env-tiers

Conversation

@tamnd

@tamnd tamnd commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Every block in every lesson has carried an env= attribute since the first one. The tool parsed it into Block.Env, stored it, and read it nowhere.

That is a hole with no symptom today, because every block in the repository says E0. It gets a symptom the first time somebody writes env=E1: the block runs under E0 anyway, the run succeeds, and the page pins output produced by the wrong runtime while saying in its own front matter that it used the right one. Wrong in the direction of looking correct is the worst kind of wrong this project can be, since the whole claim is that a machine checks what the pages say.

This closes it. The M1 checklist line is "the xray command line, enough of it to run and capture a lesson on every declared configuration", and until now nothing in the tool knew what a declared configuration was.

One file says what a lesson is allowed to need

environments.json at the top of the repository, cheapest first. A block naming an id that is not in it fails the build.

Id What it is What it costs
E0 The stock SDK, installed the way anybody installs it. One install.
E1 The release runtime with a checked clrjit beside it, chosen with DOTNET_JitName. This is what DOTNET_JitDump needs. One download of about thirty megabytes.
E2 A clone of dotnet/runtime at the pinned commit, built. Hours, and tens of gigabytes of disk.

This reorders E1 and E2 relative to the one line that used to be in docs/lesson-format.md, which said E1 was a runtime built from source and E2 was a checked build. That line was written before the checked JIT probe ran. The probe measured a checked clrjit dropping into a stock release runtime on all four platforms, which makes it a download rather than a build, and therefore cheaper than a source build rather than more expensive. A numbered ladder whose rungs are not in cost order is not telling a reader anything, so the numbers now go cheap to expensive. That is the one judgement call in this pull request and it is the reason the build lesson stays at 97 of 99.

What the build does with it now

resolve loads the declaration, works out which configurations this machine has, and puts it on the first line of every build:

resolve   .NET 10.0.11 on osx-arm64, SDK 10.0.400, pin.json holds no version yet so no citation resolves, E0 here, E1 E2 not

execute leaves out any lesson needing something this machine does not have. Leaving one out means it is named in the log with the reason and it produces nothing at all, so the check step never compares a committed page against a run that did not happen.

Three rules keep that honest.

A skipped lesson has to have its page and its captured output already committed. Without this, a missing environment would be a way for a lesson nobody has ever built anywhere to sit in the repository looking fine.

A lesson needing more than the stock SDK has to say so on its own page with {{needs}}, which writes the notice out of environments.json. This is the CONTRIBUTING rule that used to say "a lesson that needs a runtime build without saying so in the second block, in bold, with the time and the disk cost", and it was enforced by somebody remembering it in review. It is enforced by the build now, and the notice cannot promise one configuration while the code runs in another because both come from the same file.

The env field in a lesson's front matter has to match the most expensive thing any of its blocks asks for. A lesson says what it needs in two places, and two places that are never compared is one place plus a decoration.

A blueprint generator is held to E0 outright. A blueprint is a specification generated from what the runtime and its libraries already publish, so a generator needing a built runtime would be a specification only its author could regenerate.

A command that answers the question directly

$ dotnet run --project tools/xray -- env

  E0  here      the stock SDK
        this is the SDK the tool is running on, so it is here by definition
        One install. Nothing after that. See README.md.

  E1  not here  a checked JIT beside the stock runtime
        there is no checked-jit.json beside the runtime at ..., and XRAY_CHECKED_JIT does not name a file that exists
        One file of about thirty megabytes, and roughly twice the JIT time while it is switched on. See docs/probes/nightly-checked-jit.md.

  E2  not here  a runtime built from source
        XRAY_RUNTIME_SOURCE does not name a directory that exists, and nothing else can tell the tool where a built runtime is
        Hours the first time, tens of gigabytes of disk, and a toolchain per platform. See docs/lesson-format.md.

Every absence comes with the reason for it, because "not here" on its own is the start of an afternoon rather than the end of one.

--require E1 makes it fail instead of report, and the four platform jobs in CI now run xray env --require E0 before they build anything. A job that prints what it found and carries on regardless is a job that goes green after quietly losing an environment. E0 is all the hosted runners are set up for today, and that line is the one that changes when that changes.

The picture

What a lesson is allowed to need

Proving the rules do something

check --selftest goes from nine cases to fourteen. The five new ones point a block at a configuration nobody declared, make the front matter disagree with the blocks, remove the {{needs}} from a lesson that needs one, skip a lesson for a missing configuration when its pages have never been built, and skip one when they have. The last of those has to pass, and it is the one that keeps the other four from being a harness that fails at everything.

ok    passes an untouched lesson
ok    refuses a number changed by hand in a captured output
ok    refuses a captured output deleted
ok    refuses a sentence added by hand to a generated page
ok    refuses a captured output left behind by a block that no longer exists
ok    refuses a build that cannot say what it is pinned to
ok    refuses a block declaring an environment nobody declared
ok    refuses a lesson skipped for a missing environment that has never been built
ok    passes a lesson skipped for a missing environment whose pages are committed
ok    refuses a lesson that needs more than the stock SDK and never says so
ok    refuses front matter and blocks disagreeing about what a lesson needs
ok    passes an untouched blueprint
ok    refuses a number changed by hand in a generated blueprint section
ok    refuses a sentence added by hand to a generated blueprint page
xray check --selftest: 0 failure(s)

To declare a configuration nothing on this machine can provide, the harness has to add a rung to a copy of environments.json, which is how the ladder rule that ids run from E0 upward with no gaps gets exercised too.

One unrelated fix

Pointing the build at a single file, as in build docs/diagrams/environment-ladder.dg, crashed with a raw stack trace out of the cite step, which was asking the operating system for the markdown underneath a file. It says nothing and moves on now. Found while generating the diagram above.

Checked locally before this went up

xray lint: 19 file(s), 0 problem(s)
xray check --offline: 46 file(s), 0 problem(s)
xray cite: 0 citation(s), 0 problem(s)
xray numbers lessons: 2 lesson(s), 0 problem(s)
xray numbers --selftest: 0 failure(s)
xray cite --selftest: 0 failure(s)
xray assert --selftest: 0 failure(s)
xray check --selftest: 0 failure(s)
dotnet format --verify-no-changes: clean

Plus check ../lessons run from inside docs/, which is the bug #34 went in to catch and which the front matter reader could have brought back.

Every block in every lesson has carried an env= attribute since the first
one. The tool parsed it, stored it, and read it nowhere. Every block in the
repository says E0 and nothing has ever checked that, so the first block to
say E1 would have run under E0 anyway and the page would have quoted output
from the wrong runtime while saying it came from the right one.

The list of configurations now lives in environments.json rather than in a
sentence in a document, and the build reads it.
@tamnd tamnd added this to the M1 The toolchain milestone Sep 5, 2026
@tamnd tamnd added kind/tooling xray, the generators, the widgets and the checkers priority/p0 Blocks the current milestone area/build The build matrix, containers, CI and the drift bot labels Sep 5, 2026
@tamnd
tamnd merged commit 6614c77 into main Sep 5, 2026
17 checks passed
@tamnd
tamnd deleted the env-tiers branch September 5, 2026 18:26
@tamnd tamnd mentioned this pull request Sep 5, 2026
7 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/build The build matrix, containers, CI and the drift bot kind/tooling xray, the generators, the widgets and the checkers priority/p0 Blocks the current milestone

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant