Skip to content

banner: every line says where its fact came from - #67

Merged
tamnd merged 1 commit into
mainfrom
banner-says-where-each-fact-came-from
Sep 6, 2026
Merged

banner: every line says where its fact came from#67
tamnd merged 1 commit into
mainfrom
banner-says-where-each-fact-came-from

Conversation

@tamnd

@tamnd tamnd commented Sep 6, 2026

Copy link
Copy Markdown
Owner

The banner is the first cell of every lesson. It is the thing a reader looks at before deciding whether to believe a single line of anything below it, and it had four facts in it, two of which were a string somebody typed.

M1 asks for it to show version, architecture, profile, preemption model and which backend is live. It now shows all five, and the part worth having is that every line says where its fact came from.

What it looks like

This is the real output of a real boot in headless Chrome, not an example typed into a README:

kxbox: v86 backend, teaching profile
       v86, teaching profile built as A-full
       the kernel says: Linux 7.2.2 #1, PREEMPT
       and no SMP, so one processor and no true concurrency
       built for 32 bit x86 (i386), per its fragments
       teaching gives you ftrace, kprobes, BTF, every /proc file the book reads, and modules
       and costs nothing beyond its size, which is what makes it the default
       timing is emulated, so no performance claim can be made from this machine

Off a recording it says the recorded kernel said instead of the kernel says, then why the emulator was not used and whether what it is handing back is evidence.

Where each fact comes from

The release and the preemption model are read off /proc/version. That is the kernel's own one line description of itself and it is the only part of the banner a kernel can answer. Version grew three properties for it: tags is the shouted words the build appended after the build number, smp is whether SMP is among them, and preemption is whichever of them starts with PREEMPT.

Absence is the answer in two places and both are said out loud rather than guessed at. A kernel with no SMP in its banner is a uniprocessor build and there is nothing that says so positively. A kernel built with no preemption says nothing rather than saying PREEMPT_NONE, so the banner prints no preemption model in its banner, so PREEMPT_NONE instead of pretending it read one.

The preemption model is not decoration. On a PREEMPT_RT kernel most spinlocks sleep, and on a PREEMPT_NONE kernel a task in the kernel runs until it gives way, so a whole class of race cannot be reproduced. A reader who tries anyway concludes the book is wrong.

The architecture is copied out of the config fragments, and the line says per its fragments. There is nothing in /proc/version that names an architecture and nowhere else in the guest says it plainly, so this one cannot be asked of a running kernel. Printing it next to two facts that were read off a kernel, in the same voice, would let a reader believe a config file had been checked against something. It has not been. This is not like the KASAN story either: an architecture is a choice Kconfig either honours or refuses to configure at all, so what the fragment asked for is what the kernel is.

The release is never filled in from pin.toml. The pin is what somebody asked to be built and this project has already had one case of a build quietly not being that, which is the whole of the KASAN story in kernel/README.md. When the kernel that answered disagrees with the version the pin asks for, the banner says so and says that nothing in the session backs a claim about the pinned kernel.

Getting that comparison right needed a second thing. A pin row does not name a version, it names a tree: five rows say kernel = "kernel" and mean 7.2.2, and C-longterm says kernel = "fallback" and means 6.18.48. The first version of this read the version off the top of the file and would have held the longterm build to the wrong number.

Two hardcoded claims went away. LIMITS used to say uniprocessor, 32 bit x86, emulated timing and V86.describe() used to end uniprocessor, 32 bit, emulated timing. Two of those three are now read, so what is left in each is the one thing no file in the guest can tell you: a number of seconds measured inside an emulator is a number about the emulator.

A session that cannot read /proc/version at all prints one line saying so. banner() never raises, because a session that cannot say what it is running is still a session worth having, and a lesson whose first cell throws is a lesson the reader closes.

The recipe

A sixth Tier 0 recipe, banner, pointing at the /proc/version capture that came off the same boot as the three traces beside it. It is the only recipe in the file that exists for something other than a lesson, and it is repeatable in a way none of the others are: nothing about that file changes between the first read of a boot and the thousandth, which is what makes it safe for a call every lesson makes in its first cell.

Two things only the browser could find

Neither of these can fail on a machine running the test suite. Both fail on the first machine a reader uses, which is why the run in a real browser is part of this.

Adding the recipe hung the comparison. bothways traces whatever it is given, and a trace with no function filter is a trace of the whole kernel. The guest filled its ring buffer faster than the serial line drained it, and the run came back sixty one seconds later with the guest did not finish ": > /tmp/.kx.b64" within 20000ms, which is a timeout on an unrelated shell line three files from the cause. A recipe that records a file rather than a tape now goes through by_file, which runs the command and compares the bytes of every file it captured, exactly, because there is nothing there two honest runs are expected to disagree about. Back to seventeen seconds, and the comparison it does is the one that was meant.

The two lockdep recipes were in the same position and nobody had noticed, because they are a different profile and compare had never been pointed at them.

import kxwidgets died in Pyodide. kxray.replay.record drives a pseudo terminal, so it imports fcntl and termios, and a browser has neither. It was imported at the top of kxray/replay/__init__.py, which kxwidgets reaches through on the way to the session player, so a page that only wanted to draw a call tree got ModuleNotFoundError: No module named 'fcntl' and no toolkit at all. It is loaded on demand now. A test blocks fcntl, termios and pty and imports every package the worker imports, so the next module that reaches for one of them fails on a laptop.

The Colab reader

profiles looked for kxbox/kernel/pin.toml under the working directory and nowhere else. The badge on the front page runs pip install git+https://... on a machine with no checkout on it, so every one of those readers would have been told the architecture was an architecture nothing in the fragments names. The wheel carries the pin and the fragments next to profiles.py and it only needed asking for. A checkout still wins when there is one, because that is the copy somebody might be editing.

Verification

node kxbox/web/measure.js in headless Chrome on the pinned A-full image, which is where the block at the top came from:

boot to ready 3.0s
checks passed 11 of 11
Python up 3.13.2 in 1.7s
both ways 17.2s, 4 of 4 recipes agree
one traced recipe 41 calls in 7.1s

The row is in kernel/RESULTS.md with the note about why it counts four recipes and eleven checks where the rows above it count three and ten.

The live wording of the banner exists only on a machine with a kernel behind it, so first-tape.py prints it and index.html shows it under The banner, off a live kernel. A test holds the page to printing it, because the live branch is the one no test on a laptop can reach and it is the branch every reader gets.

Checked

1508 pytest tests, 63 node tests, ruff clean and formatted, and every gate: baseline 41 artefacts and 7968 lines with 0 unparsed, claimledger, bpc, kconfig, refcheck, lintnb, lintprose, kxmanim, and the six --check runs.

measure.js also takes KXBOX_PORT now. The failure when something else holds 8123 is the server did not come up within 30s, which points at the emulator and the wheel long before it points at another program holding a socket.

The banner is the first cell of every lesson and it had four facts in it, two of
them asserted in a string. It now reads the release and the preemption model off
/proc/version on both backends, takes the architecture off the config fragments
and says so, and never fills the release in from pin.toml.

Verified against a real v86 boot in headless Chrome, which is the only place the
live wording exists.
@tamnd
tamnd merged commit 0a10438 into main Sep 6, 2026
3 checks passed
@tamnd
tamnd deleted the banner-says-where-each-fact-came-from branch September 6, 2026 11:22
@tamnd tamnd mentioned this pull request Sep 6, 2026
20 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant