Read from src/data/receipts.json, which bun run check writes when it finishes. Nobody types these in. A failure stays on this page until a later run passes.
All 18 checks passed
Ran 5 November 2026, 22:25 UTC. The whole run took 17 min 2 s.
A ratchet only turns if each mistake leaves something behind. This is the log of real things that went wrong while building this site, what noticed, and what changed. Of these 17, 4 were caught by a check we had already written. The rest needed something else, and that is worth knowing before you trust a green panel.
A check we had written
4
Comparing with the real thing
2
An agent looking at the screen
1
Opening the source
1
Results that looked wrong
2
We ran into it
6
A person reading the field
1
The live site broke a rule no local check could see
What went wrong
Cloudflare Web Analytics is on for the dqnotes.com zone, so Cloudflare adds its beacon script to every page at the edge. Our Content Security Policy blocked it, which put a console error on every page, but only in production.
What caught it
We ran into it. Opening the live site in a browser straight after the first deploy. Every local check, including the browser sweep of every route, had passed, because the script is never there locally.
What changed
The policy now allows exactly the beacon's two origins, with the reason in public/_headers. scripts/check/live.mjs opens the live site after every deploy, and the deploy script now runs the full check first: the first deploy script skipped it.
Still open. Anyone can still run wrangler deploy by hand and skip both, and CI has no deploy job.
The experiment’s reference solution for the comment renderer failed the hidden scenario “a quote cannot break out of the link”. The solution was right: it escaped the quote. The scenario’s regex matched the encoded quote anyway, so any program that escaped correctly would have been scored as broken.
What caught it
A check we had written. The instrument test, which requires every reference solution to pass every scenario of its own task before any agent is measured.
What changed
The scenario’s regex, which now only matches a real break out of the href. The test stays, because the experiment’s scores are only as good as its scenarios.
tests/experiment.test.ts, experiments/tasks.ts
The experiment kept restarting the dev server
What went wrong
The experiment’s temporary workspaces lived in the repo, each with its own tsconfig.json. Vite logged “changed tsconfig file detected”, cleared its cache and reloaded every time one appeared, and server-rendered pages answered 500 with “Cannot read properties of null (reading 'useContext')”.
What caught it
We ran into it. Pages failing on the dev server, and the server’s own log, which named the path experiments/.work.
What changed
The workspaces moved to the system’s temp folder, and the Vite config now tells its watcher to ignore experiments, docs, shots and scripts.
experiments/run.ts, vite.config.ts
The first task set was too easy
What went wrong
Claude Haiku 5.5 passed nearly everything from a vague brief, because the tasks were famous problems whose function signatures gave the answer away. An experiment about wording would have found nothing, for a boring reason.
What caught it
Results that looked wrong. The first results, which were too good to tell the conditions apart.
What changed
We replaced them with tasks that have traps in them, and kept two of the originals as controls. Choosing harder tasks after seeing results is a real weakness in the experiment, so it has to be disclosed alongside the numbers.
experiments/tasks.ts
Our reviewer prompt was biased, by us
What went wrong
The first reviewer prompt asked it to find a real defect. In a separate test, one correct function reviewed 40 times was approved 33 of 40 by a neutrally worded prompt and 1 of 40 by ours. A buggy function was rejected 40 of 40 under both, so on that function the harsher prompt caught nothing extra.
What caught it
Results that looked wrong. The reviewer gate’s results looked strange, and that is the only reason anyone tested the prompt. No check would have flagged it.
What changed
A neutral re-review of the recorded runs, published next to the original: of 18 broken and 222 good one-shot programs, the neutral prompt stopped 13 and 39, and ours stopped 16 and 126. The two prompts differ by more than one sentence, and this is one small model on one-function tasks, so the size of the gap is not a general result.
The bench kit includes a real mutation tester, so we pointed it at itself and at the metrics. The first score was 83.9%: 146 of 174 mutants killed and 28 alive.
What caught it
A check we had written. scripts/check/mutation.ts, running each mutant against the tests in its own throwaway copy.
What changed
More tests. It is now 94.3%, 164 of 174, with a floor of 90% in the check, taking 31.0 s on the last run. 10 survive. All 10 look equivalent, judged by reading them and not by proof: eight turn < into <= on a loop bound, where the extra pass changes nothing.
Still open. 6 mutants don’t parse, so they aren’t counted. On the last reading, 4 of the 6 come from a real gap: the tester reads ** as two separate * tokens and turns one into /, so it can’t mutate the exponents in the crap function. Those exponents have tests, and no mutant tests the tests. The other 2 are the angle brackets of a generic type, read as comparisons.
Two gaps in the security policy that a console wouldn’t show
What went wrong
The first policy left out blob: in script-src, which the reading chapter’s benches need to load real modules inside their workers. Separately, without 'unsafe-eval' a sandboxed bench fails with nothing in the console: each run quietly returns an error.
What caught it
A check we had written. The browser sweep, through one failing reading-chapter bench. That one bench is the only reason the blob: gap was found.
What changed
blob: went in, each allowance was tested by removing it from a production build, and the reasons went into public/_headers. The sweep now asks directly, by starting a blob: worker and seeing whether new Function runs in it.
public/_headers, scripts/check/site.mjs
Old pages kept coming back
What went wrong
Vite only empties an output folder that is inside the repo. Building into a scratch folder left the old pages there, the prerenderer was served one of those old pages, and it saved that page straight back into the new build.
What caught it
We ran into it. Building into a scratch folder so that the sweep couldn’t disturb anyone. Vite’s own warning about this was in the build output.
What changed
scripts/check/all.ts passes --emptyOutDir whenever it builds outside the repo.
scripts/check/all.ts
What every automated check passed, and a look did not
What went wrong
Simulation labels nobody could see. A note saying “on the left”, which was wrong on a phone, where the panel it meant sits above it. In the backpressure chapter’s review of its own benches, a note saying a neutral reviewer approved 40 of 40, when the recorded figure is 33.
What caught it
An agent looking at the screen. Agents opening their screenshots at 1440 and 390 and reading them. The sweep measures overflow and console errors. It has no opinion about whether a sentence is true.
What changed
Each was fixed where it was found. Looking is in the definition of done for a chapter, and that is a rule in a file: it depends on an agent doing it, and nothing checks that one did.
AGENTS.md, scripts/shoot.mjs
Chapter engines, held against the real tools
What went wrong
The git chapter’s worktree bench said agent B had nothing left to commit. Real git showed B does commit, and agent A’s commit lands on the branch called cart, carrying B’s half-done file.
What caught it
Comparing with the real thing. Running the same commands in real git 2.54 beside the bench. The bench shell got the same treatment against real bash and now agrees on 158 of 158 lines, after differences in ls argument order, ls -d and PATH= ls. The type bench’s small checker is compared with real tsc on every program it shows.
What changed
The note and its panel were corrected, and the shell’s differences were fixed. Only the tsc comparison is a test that runs every time. The git and bash ones were scripts run while writing those chapters.
A Hashimoto quote cut off mid-sentence. “Only exit 2 blocks”, out of date for Claude Code hooks since the onFailure setting in v2.1.295. A pstack line credited to encode-lessons-in-structure when it comes from the skill called correct. Our own research file misquoted Elicit and Assembled, and a chapter trusting it would have repeated both.
What caught it
Opening the source. Agents opening the primary source for each claim before writing it, as AGENTS.md asks.
What changed
The chapters carry the words on the source’s page. Corrections that override the research files now sit in AGENTS.md.
The security chapter explains why 7 of 29 recorded comment renderers from vague briefs produce a javascript: link. (Its recorded alert(1) gets cut at the first closing bracket and doesn’t run. A payload with no brackets ran, from the same 7.) The first explanation written down was a guess, and it was wrong. Those programs do allowlist schemes. They fail because they treat a link with no visible scheme as relative, and a control character hides the scheme from their regex.
What caught it
Comparing with the real thing. Checking the guess against the recorded programs before it shipped.
To clear a hung command of its own, an agent ran pkill -f "bun test". That pattern matches every process on the machine, so it may have killed other agents’ test runs. The same agent ran rm -rf on a shots folder in the shared scratch space, where another agent’s script was also writing.
What caught it
We ran into it. The agent’s own end-of-task report, which said so. We then checked whether the experiment was contaminated. It wasn’t: its last run was recorded at 09:54 UTC and the pkill was at 10:11 UTC. Three attempts with hanging tests had looked suspicious, and they reproduced on their own, so those tests really hang.
What changed
A rule in AGENTS.md: no pkill, killall or rm -rf on anything you didn’t create, and scratch files go in your own subfolder.
AGENTS.md, experiments/results/main.jsonl
One bad sed took down every page
What went wrong
A bad sed put a syntax error in one chapter’s words.ts. Every page imports every chapter’s words, so every route on the shared dev server failed for a minute or two, for every agent working at the time.
What caught it
We ran into it. The agent that did it reported it. Other agents reported the same kind of site-wide 500 from syntax errors in other chapters.
What changed
A rule in AGENTS.md: typecheck right after a bulk edit, and never run a blind sed over a words.ts.
Still open. That’s a line in a file, and the same file says a line is the last resort. No check stops this happening again, and a per-chapter error boundary would.
AGENTS.md, src/parts/index.ts
A test result landed on the wrong bug
What went wrong
The git chapter’s bisect bench runs each test in a Web Worker and applies the answer when it comes back. Its check that the answer still applied compared only the midpoint and the size, and every new bug of the same size starts at the same midpoint. Press New bug while a test was running, and the old bug’s verdict was marked on the new search: one test counted that nobody ran on it, and a search that could now blame the wrong commit.
What caught it
A check we had written. Biome’s useExhaustiveDependencies flagged the effect for leaving out runTest and reset. Those two omissions were harmless. Reading the effect to fix them is what found this, and slowing the Worker in a production build showed it happening: Tests so far read 1 on a search that had run nothing.
What changed
An answer now counts only if the search that asked for it is still the current one, and a test whose bug has gone says nothing at all.
Still open. No test holds this in place. The chapter has no component tests, so the fix was checked by hand in a browser, at 1440 and at 390.
src/parts/git/benches.tsx, biome.json
A whole topic was missing, and nothing flagged it
What went wrong
The course taught tests from examples to properties and stopped there. Model checking, formal verification and statecharts had no terms, and neither did memory safety. Eighteen chapter agents, a research pass and a layman’s review all worked on this site, and none of them said so.
What caught it
A person reading the field. The owner, reading three posts from early November 2026, saw people arguing about formal verification and registries in words the course didn’t have.
What changed
The tests chapter now climbs from examples to properties, to every state, to proof, with a real model checker, an exhaustive verifier that proves the wrong spec, and a statechart. Memory safety, code registries, review in the abstract and reading a forecast have terms too.
Still open. No check can catch this kind of gap, and we have not written one that pretends to. Every check here compares the site with something already written down: a source, a rule, a list of terms. None of them knows what should be there and isn’t. That still takes a person who reads the field, and nothing tells us when the next topic is missing.
Following the course for real broke the kit its own check had passed
What went wrong
On 5 and 6 November 2026 an agent built the /project app from an empty folder with a real model, using the kit as a reader downloads it, a chapter at a time. Four things in the kit were broken. At chapter 9 the check script ran no tests, because its tests gate came with chapter 10’s files. Chapter 6’s deny rules covered all of .claude/hooks/, so the agent couldn’t add chapter 9’s stop hook. The stop hook sent the agent back with the same words until Claude Code’s limit, and its final message was empty. ralph.sh called green checks done with every plan item still open. Two more were nearly as bad: the loop’s agent had no permission to commit, which the loop prompt asks it to do, and AGENTS.md said CI runs every gate in downloads that had no CI at all. Running the chapter 9 prompt again on the fixed kit found one more: early downloads’ AGENTS.md, ratchet log and package.json named scripts those downloads didn’t have yet.
What caught it
We ran into it. The agent ran into each one and worked round it in its own prompts, so the files a reader downloads still had them. The kit’s check rendered every chapter’s download and ran the whole one and two partial ones, but never a chapter’s download on top of an earlier one, never a red suite held to Claude Code’s limit, never a plan left open with the checks green, and never a loop pass inside the real Claude Code program.
What changed
Each is fixed in both stacks, and each has a check in scripts/check/kit.ts that was run on the old files first and failed there: the chapter 9 check has to run a test and refuse a failing one, the deny rules are tried against every later chapter’s new files, the stop hook has to ask for a final message on its 8th block in a row and still block, ralph.sh has to keep going while an item is open, a loop pass has to commit inside real Claude Code, AGENTS.md may mention CI only where the download’s CI runs every gate, and a download’s AGENTS.md, ratchet log and package.json may name only files it has. KIT_TEMPLATES lets anyone rerun the check on an old copy.
Still open. The stop hook’s final message and the loop’s commit were seen with a script standing in for the model, inside the real Claude Code program. Nobody has yet seen a real model given that last block. The /project page keeps the run as it happened, with a note under each stage saying what changed since.
A definition can pass every check and still be wrong. Nothing here understands the sentence. Most of the mistakes in the log above were found by something other than a check.
Whether a quote is fair
The facts check confirms that the words are on the page. It can’t see a quote that’s accurate and misleading, or a source that is itself wrong.
Quotes outside one field
Only a term’s seen field is searched, and only for quotes of 12 characters or more. Words in a bench’s notes or a term’s other fields aren’t fetched against anything.
What a link says
A status under 400 means something answered. It doesn’t mean the page still says what we cite it for.
Most of a bench
The sweep presses the first control of each bench once. Every later control, and every combination of them, is unvisited by the sweep.
Real browsers and real hands
The sweep is Chromium with emulated touch. Safari, Firefox, a real phone and a screen reader aren’t in it.
The chapters’ logic under mutation
Mutation testing covers two files of the kit. A chapter’s tests could be as weak as the kit’s first ones were, and this score wouldn’t move.
What nobody asked for
The experiment’s own lesson applies to this site too: a mechanical gate can’t catch a requirement no one wrote down.
Comparisons that ran once
The bash and git comparisons in the log were scripts an agent ran while writing the chapter. They aren’t in bun run check, so nothing would notice if those engines drifted. The tsc comparison is a real test and runs every time.
Every quote on this site is meant to be verbatim, and someone may repeat one to an interviewer. So a script fetches each source and looks for the words. It needs the network, so it isn’t part of every run, and what it last found is published here with its date.
Quotes
1078 fetched, 5 November 2026, 21:04 UTC.
verified: the words are on the page
1078
not found: the words aren’t in what we were given
0
fetch failed: at least one source couldn’t be read
0
Links
654 fetched, 5 November 2026, 21:04 UTC.
answered
644
refused the robot, so not verified
10
couldn’t be reached
0
dead
0
What verified means
The exact words are in the HTML of one of the term’s sources, ignoring case, spacing and typographic quotes. It says nothing about whether we quoted them fairly, or whether the source is right.
What not found means
The words aren’t in the HTML we were given. That can be a real misquote. It can also be a page built by JavaScript, a citation of a GitHub folder when the words are in a file inside it, a quote from another source than the one listed, or our own phrasing in quotation marks.
What it doesn’t mean
Not found isn’t proof of a misquote, and nobody has cleared this list. Each row still needs someone to open the source. Fetch failed proves nothing either way: mostly sites that answer a script with 403, and PDFs this check can’t read.
Counted from the chapters each time the site is built, except where it says the last run.
chapters
1818
terms
526526
benches
373373
gate questions
108108
drills
5555
sources cited
11251125
unit tests on the last run
676676
routes swept on the last run
553553
Cost and time
The one real figure in this repo: the main experiment spent $2.51 on 1097 model calls across 480 runs, over 33 minutes starting 5 November 2026, 09:21 UTC. What building the site itself cost, in money or hours, isn’t recorded in this repo, so there is no number for it here.
.github/workflows/ci.yml is written and its actions are pinned, and it has never been executed. It also holds the only bun audit, so the dependency audit isn’t part of what the first section reports.
Lefthook has never run in a real git repository
lefthook.yml is written, and this working folder isn’t a git repository, so no hook has fired. The pre-commit job calls gitleaks, which isn’t installed on the machine this was built on, so that job will fail until it is. No secret scan has run.
No real user has been through this course
Nobody has started at chapter 1 knowing nothing and come out able to defend their work. Whether it teaches what it says it teaches is untested.
No human has reviewed every line
The checks ran over all of it and other agents reviewed parts of it. The one person directing them hasn’t read it all, and no outside reader has either.
Security posture
The policy on this site is weaker than one you would want on an app with accounts. Its allowances exist because the benches run real code in your browser. The defence is that the site takes no input from other people and loads one third-party script, Cloudflare’s analytics beacon, which Cloudflare adds to every page for this domain and the policy names by its exact origin. Each allowance below was tested by removing it from a production build in Chromium. The ways to tighten them are ideas and haven’t been built.
TanStack Start writes a small bootstrap script into every page, and it differs from page to page.
Take it away and
The bootstrap is blocked and the page throws a TypeError.
To tighten it
One hash per page doesn’t fit the limit of 100 rules in the headers file. It would take a nonce or hashes from the Worker, which means waking the Worker for pages that are served statically today. Not tried.
script-src 'unsafe-eval'
Why it’s there
Benches run real code with new Function inside a blob: Web Worker, and a blob: worker inherits the page’s policy, so this can’t be allowed for the worker alone.
Take it away and
Nothing shows in the console. Every sandboxed run just returns an error.
To tighten it
Serve the sandbox as a same-origin worker file with its own policy, so pages can drop this. That means changing src/bench/run.ts and two chapters. Not built, so not tested.
script-src blob:
Why it’s there
The reading chapter’s benches load real modules and scripts from blob: URLs inside those workers, so that a stack trace names a real file.
Take it away and
Those requests fail with “csp”.
To tighten it
Change those benches so they don’t load from blob: URLs, and give up a stack trace that names a real file.
worker-src blob:
Why it’s there
The same benches create their workers from blob: URLs.
Take it away and
Creating a worker is refused.
To tighten it
The same worker file would let this become worker-src 'self'.
img-src blob:
Why it’s there
The performance chapter’s image bench shows the picture it just compressed from a blob: URL, which the page made itself.
Take it away and
The picture is refused and the bench shows a broken image.
To tighten it
Have that bench report sizes only and show no picture, which is most of what it teaches with.
style-src 'unsafe-inline'
Why it’s there
The @font-face block in the page head, and React’s style attributes on the server-rendered HTML.
Take it away and
The font block and every style attribute are refused.
To tighten it
Move the font block into a stylesheet and every style attribute out of the server-rendered HTML. Wire colours and bench layouts use them throughout, so this is the largest of the five. Not tried.
One gap in coverage. These headers come from public/_headers, which only applies to static files. A response the Worker makes itself doesn’t get them. On this site that means the 404 page for an address nothing was prerendered for. It runs no benches, and the only thing it prints back is the address, which React escapes. It still has no policy, and fixing that means setting the headers in the Worker as well.
The repo isn’t public yet, so for now you can’t run these yourself. They’re here so you can see exactly what would run, and they’ll work as written once it is. Until then, the first section is the only evidence you can see, and you’re taking the files behind it on trust.
Set up
The same two steps CI uses.
$ buninstall--frozen-lockfile
$ bunxplaywrightinstall--with-depschromium
Everything
This runs every check in the first section and writes src/data/receipts.json, which this page reads, so rebuild afterwards to see your own result here.
$ bunruncheck
The two that need the network
These fetch every external source and rewrite the records in src/data. They aren’t part of every run. A rerun is what would clear or confirm the lists in the facts check section.
$ bunscripts/check/links.ts--external
$ bunscripts/check/facts.ts--external
Look closer
The first lists every mutant that didn’t parse. The second rewrites the bench height record, for when the sweep reports page jumps, and you rebuild after it.