lupp.us has no release tags; an entry here is a merged sprint, in the shape D65 rules: user-visible changes only, the sprint id named.
This file is rendered to /changelog/site/ from the checkout the build pins, and the render happens BEFORE the stamps run. An entry that spells one of the build's placeholder tokens out — the version ones, the KiB ones, the count ones — therefore has it filled in on the served page and says something else there than it says here, and one of them stops the build outright. Name the source in words; do not quote the token.
Because it is served prose, BOTH of the site's prose audits walk this file as a second root (--also CHANGELOG.md, wolf-web#27). Every number word an entry spells out needs its own line in scripts/count-allowlist.txt, and every version literal an entry names needs one in scripts/version-allowlist.txt, both keyed on CHANGELOG.md. Those lines carry no clock — counted=0 for a word, frozen for a literal — because an entry records what one wave measured at one set of pins, and no later pin can make that false. The audits are not asking you to re-read them. They are making you derive a number, and check a version, before you serve it.
ww42 (the site at 0.2.29, a wider lag, and the shell's signals) — 2026-10-13
All three gitlinks move: the book to bs66's merge, the compiler to wolf 0.2.29, and the interpreter to lupin 0.1.52.
The lag widens by a release; the distance is 611 commits. lupin 0.1.52 kept the specification pin lupin 0.1.48 first took, the v0.2.24 tag, while the compiler tagged 0.2.29, so the ancestry guard answers gap 5, tagged true, 611 commits, where it counted 541 at the last bump. Its closed set of phrases stopped one short again, so at the compiler's pin and at the interpreter's it stopped, naming the phrase no page had. Both pages' sentences were written first, then the phrase joined the set.
The tab still refuses nothing the compiler runs. Every phase: run program in the pinned corpus went through the module this build publishes, both corpora through both modules: 659 programs, 487 exit, 47 trap, 125 unsupported, 0 fail, 534 candidates, and no instance killed. The release brought one program into the running set, os/signal_shell_rows.lu, and the tab declines it at its first call, a poll of a signal set, with the process tier; at a terminal lupin 0.1.52 prints its header. No program both corpora carry changed its verdict, and all 659 print the same under lupin 0.1.51's module and lupin 0.1.52's.
What the tab answers for the release's new calls. os_pid, os_ppid, os_signal_send, os_signal_number and os_signal_ignored are hosted calls, and a program that makes any one of them here is answered unsupported, naming the call: the WebAssembly build declines them with the process tier. Under lupin 0.1.51's module each was a name that does not resolve. At a terminal lupin 0.1.52 serves the two ids and the number and refuses the send and the ignored set by name. /play/ and /install/ say so.
lupin 0.1.52 still does not compile to WebAssembly as released (wolf-interp#222, open). The portability patch applies unchanged to the staged copy, and the module builds with no imports.
Sentences that named the wrong release. The front page leads with what 0.2.29 added (a task parked for every connection, the shell's signals, a language server that finds the standard library) and says the archive's std.signal predates the new meanings; 0.2.28's story moves into the history. /spec/ binds 03's restated task cost and 11's signal clauses to the stamp and dates 0.2.28's clauses. linux aarch64 holds as written: the 0.2.29 release's arm gate asserts the same refusals and the same checked run.
ww41 (the site at 0.2.28, four releases of lag, and std/ in the archive) — 2026-10-12
All three gitlinks move: the book to bs65's merge, the compiler to wolf 0.2.28, and the interpreter to lupin 0.1.51. The site skipped 0.2.27 and 0.1.50, so both releases' surfaces arrive at once.
The lag widens to four releases; the distance is 541 commits. lupin 0.1.50 and 0.1.51 kept 0.1.49's specification pin, the v0.2.24 tag, while the compiler tagged 0.2.27 and 0.2.28, so the ancestry guard answers gap 4, tagged true, 541 commits, where it counted 213 at the last bump. Its closed set of phrases stopped at a gap of three, so at the compiler's pin it did not list wrong phrases: it stopped, naming the phrase no page had. Both pages' sentences were written first, then "four releases" joined the set, with a test that the set has no hole.
The tab still refuses nothing the compiler runs. Every phase: run program in the pinned corpus went through the module this build publishes, both corpora through both modules: 658 programs, 487 exit, 47 trap, 124 unsupported, 0 fail, 534 candidates, and no instance killed. The declines are the ones the last bump named. The two releases' additions print their headers here — the unsigned and wrapping integers and their traps, the json names that answer last-wins, env_unset, octal and binary literals, and the calls that keep or send a string and stay legal — or are declined for the tab's reasons: the calls that change a file, os_exec, jobs and the umask, a net read, a task-spawning program, and a module named c that lives in a sibling file. No program both corpora carry changed its answer.
Three answers the old tab gave are gone. Under lupin 0.1.49 the json programs answered a repeated name first-wins, os/env_unset.lu was declined and typecheck/int_truth_wrap_mul_wide.lu trapped overflow where the compiler prints the wrapped product; lupin 0.1.51 answers each as the compiler does.
lupin 0.1.51 still does not compile to WebAssembly as released (wolf-interp#222, open). The portability patch the last bump added applies unchanged to the staged copy, and the module builds with no imports.
The archive carries the standard library. Since 0.2.27 every archive holds std/ beside wolf, the default std root. /install/ says so where it lists the archive's folder, and the Windows job asserts std/ and its STD-REV with the rest of the folder; the front page says a source build has none.
Sentences that named the wrong release. The front page leads with what 0.2.28 closed and added and sets 0.2.27 beside it, and 0.2.26's story moves into the history. /spec/ binds 02's call rule, 03's checked concurrency and channel rule and 11's file-changing calls to the stamp, and dates 0.2.26's and 0.2.27's clauses. linux aarch64 holds as written: the 0.2.28 release's arm gate asserts the same refusals and the same checked run.
ww40 (the site at 0.2.26, two releases of lag, and linux aarch64 told as it is) — 2026-10-09
All three gitlinks move: the book to bs64's merge, the compiler to wolf 0.2.26, and the interpreter to lupin 0.1.49.
The lag widens to two releases; the distance is 213 commits. lupin 0.1.49 kept 0.1.48's specification pin, the v0.2.24 tag, while the compiler tagged 0.2.25 and 0.2.26, so the ancestry guard answers gap 2, tagged true, 213 commits, where it counted 50 at the last bump. It went red at the compiler's pin and stayed red at the interpreter's, because the new interpreter did not move the pin; /install/ and /play/ now carry "two releases" and neither carries another phrase.
The tab still refuses nothing the compiler runs, and its declines shrink. Every phase: run program in the pinned corpus went through the module this build publishes, both corpora through both modules: 618 programs, 465 exit, 40 trap, 113 unsupported, 0 fail, 505 candidates, and no instance killed. The atomics (fence, Order), the volatile program and the field read through a raw pointer to a struct, which the last bump named as declines waiting on the interpreter, print what the compiler prints (wolf-interp#194, #185, #205). What remains is the whole-struct raw stores, extern "c" let's link symbol and a bodyless C call, all declined by name at a terminal too. The release's own additions — raw field stores, ! on integers, -> never, copy region and the byte scan — print their headers here. One program answers differently in the tab without being declined: os/chdir_relative.lu exits 1 on the io row this build's os_chdir answers, where the interpreter at a terminal exits 0 (wolf-interp#222).
lupin 0.1.49 does not compile to WebAssembly as released. Its new os_error_text calls into the filesystem module, which the wasm build leaves out. The site's build carries a portability patch for the staged copy (never the submodule), as it was built to do: in the tab that call declines by name, as the filesystem tier does. Filed as wolf-interp#222.
No program in the tab printed an old wrong answer. The fix this release makes on the compiled tiers, a call's str result read after its region was freed (wolf-lang#618), was never wrong in the interpreter, which traps it; every program both corpora carry printed the same under both modules except the five declines that now run.
linux aarch64, as the release measures it. The front page and /install/ said its archive runs programs on the checked tier and that it has no CI runner. The second half was false: the release workflow builds and gates that archive on an arm runner at every tag, and the gate asserts both compiled tiers refuse the host by name with exit code 2 while the checked tier runs the program (wolf-lang#614). Both pages now say that, and the front page's sentence carries the release stamp so the next bump re-reads it.
Sentences that named the wrong release. The front page leads with what 0.2.26 fixed and added and moves 0.2.25's story into the history. /spec/ binds 02's copy-out, call-site, m.K, raw-field and byte-scan clauses, 08's derived capabilities, 10's never and !, and 11's descriptor, pipe, working-directory, terminal, copy and error-number clauses to the stamp.
ww39 (the site at 0.2.25, chapter 7 live, and the tab refuses nothing) — 2026-10-08
All three gitlinks move: the book to bs63's merge, the compiler to wolf 0.2.25, and the interpreter to lupin 0.1.48.
The lag is still one release; the distance is 50 commits. lupin 0.1.48 re-pinned two tags forward, on the v0.2.24 tag, so the ancestry guard answers gap 1, tagged true, 50 commits, where it counted 200 at the last bump. /install/ and /play/ carry "one release" and neither carries another phrase. The pairing in between, lupin 0.1.47 beside wolf 0.2.24, read the v0.2.22 text; this site never served it, and both pages now say so.
The tab refuses nothing the compiler runs. Every phase: run program in the pinned corpus went through the module this build publishes, both corpora through both modules: 592 programs, 443 exit, 39 trap, 110 unsupported, 0 fail, 482 candidates, and no instance killed. The refusals the site named at the last bump leave the list: the 2 layout programs that only ask size_of, align_of and offset_of or store bytes print what the compiler prints (wolf-interp#188), the 2 that store a whole struct through a raw pointer are declined by name, as the compiler's own checked machine declines them, and extern "c" let's linker symbol is declined by name because the interpreter models no link (wolf-interp#190). What remains is declines, each named on the pages: the atomics and the volatile program, whose mirrors are on the interpreter's trunk and not yet released (wolf-interp#194, #185), and a field read through a raw pointer to a struct (wolf-interp#205).
No program in the tab printed an old wrong answer. The fixes this release makes on the compiled tiers — a module var or raw memory written by a callee and read back after the call (wolf-lang#598, #601), a branch on the top half of an unsigned shift (#600), and, a release earlier, a module string holding its value (#585) — were never wrong in the interpreter: every running program printed the same bytes under the module served until now as under this one.
Chapter 7 shows memory. The book's chapter on ownership draws its trees before and after a move, a copy and a re-initialization, generated from the interpreter's own record of every place. The drawings are static SVG images, no script draws anything, so they render under the site's script-src 'self', and the reading page names them in its part list.
Sentences that named the wrong release. The front page leads with what 0.2.25 fixed, sets 0.2.24's atomics, fences, freestanding allocator, row L4, module strings and wolf prelude beside it, and moves 0.2.23's story into the history. /spec/ dates 01's last widening at v0.2.23 and adds 02's alignment row and call clause, 03's atomics and 04's allocator hook.
is71 (the tab reads cfg as an x86-64 Linux host) — 2026-10-05
The tab takes a target. The interpreter evaluates #[cfg(target = …)] against the target it was built for, and the playground's module is built for WebAssembly, which no gate in the corpus names, so the tab dropped every gated definition. Ruled B on wolf-web#55: the bridge now observes every program as x86_64-unknown-linux-gnu, the target the interpreter's own conformance bundle is observed as, and the line above /play/'s editor says so. grammar/cfg_target_arch.lu prints 64 in the tab, as it does on every compiler tier and under the interpreter at a terminal; it was declined, naming arch_bits as a function that does not resolve. /install/ and /play/ no longer name it as a parting.
What else moved. The census of every phase: run program through the module moves by that program alone: 580 programs, 434 exit, 39 trap, 102 unsupported, 5 fail. Outside the census, ffi.lu exited 1 in the tab, its inline asm gated away on both architectures, and is now declined by name for the asm, as the interpreter declines it at a terminal.
ww38 (the site at 0.2.23, the tab's refusals re-derived, and one parting that is the tab's own) — 2026-10-04
All three gitlinks move: the book to bs60's merge, the compiler to wolf 0.2.23, and the interpreter to lupin 0.1.46.
The lag is still one release; the distance is 200 commits. lupin 0.1.46 re-pinned on the v0.2.22 tag, so the ancestry guard answers gap 1, tagged true, 200 commits. /install/ and /play/ carry "one release" and neither carries another phrase.
The partings ww37 named are healed. The two cfg(target) witnesses (wolf-interp#174), the three programs with a raw pointer in a private signature or across the C membrane (#181) and the else-less if that printed the row's tag (#179) all answer as the compiler does in the tab, or, for the bodyless C declaration, are declined by name. The pages keep them named, as history.
The programs the tab answers differently, re-derived. Every phase: run program in the pinned corpus went through the module this build publishes, both corpora through both modules: 580 programs, 433 exit, 39 trap, 103 unsupported, 5 fail, 472 candidates, and no instance killed. The 5 fail rows are all clauses the compiler wrote after the text the interpreter was built to, each pinned by version in the compiler's own release and named on the pages by issue: the 4 layout witnesses for #[repr(c, packed)] and #[repr(c, align(N))], which the interpreter's closed attribute set still refuses by name (wolf-interp#188), and extern "c" let, a production the interpreter's grammar does not have yet (#190). Nothing that runs prints differently. The volatile witness is declined by name (#185).
One parting is the tab's own. grammar/cfg_target_arch.lu prints 64 on every compiler tier and under lupin 0.1.46 at a terminal, and answers unsupported in the tab: the interpreter evaluates cfg against the target it was built for, and the tab's module is built for WebAssembly, which neither gate names. /install/ and /play/ say so, and wolf-web#55 asks which target the tab should claim.
Sentences that named the wrong release. The front page leads with what 0.2.23 changed — integers and pointers both ways and *p, volatile access, packed and aligned layouts with size_of, align_of and offset_of, module state, section placement and extern "c" let, interrupts, and the else-less if at a fallible tail that discards its raise — with 0.2.22's freestanding target as the release before it. /spec/ names the new clauses under the documents that hold them: 01, 02, 04 and 10.
ww37 (the site at 0.2.22, #176 healed, and the newest clauses' witnesses named) — 2026-10-04
All three gitlinks move: the book to bs59's merge, the compiler to wolf 0.2.22, and the interpreter to lupin 0.1.45.
The lag is still one release; the distance is 222 commits. lupin 0.1.45 re-pinned on the v0.2.21 tag, so the ancestry guard answers gap 1, tagged true, 222 commits. /install/ and /play/ carry "one release" and neither carries another phrase.
The parting ww36 named is healed. rows/eu_bind_empty_row_handled.lu, which the tab refused where every compiler tier prints 43 42 42 (wolf-interp#176), runs and prints what the compiler prints. The pages keep it named, as history.
The programs the tab answers differently, re-derived. Every phase: run program in the pinned corpus went through the module this build publishes, both corpora through both modules: 559 programs, 416 exit, 38 trap, 100 unsupported, 5 fail, 454 candidates, and no instance killed. The 5 fail rows are clauses the compiler wrote after the text the interpreter was built to, each pinned by version in the compiler's own release and named on the pages by issue: the two cfg(target) witnesses (the interpreter reads no cfg, wolf-interp#174) and the three programs that carry a raw pointer in a private signature or across the C membrane (the interpreter refuses one in every signature, #181). One program exits 0 on both sides and prints differently, inside the text both read: an else-less if whose branch raises binds the row's tag in the tab where the compiler binds the unit value (#179). A nested function with a moded parameter, which the tab declined, runs (#169).
Sentences that named the wrong release. The front page leads with what 0.2.22 changed — the freestanding target, assembly in the manifest, the C membrane both ways, the closed attribute set, the narrowing cast, loop pieces held to their region, seek and tell — with 0.2.21's fallible match as the release before it. /spec/ names the new clauses under the documents that hold them: 01, 02, 04, 06, 10 and 11.
ww36 (the site at 0.2.21, and one program the tab refuses) — 2026-10-03
All three gitlinks move: the book to bs58's merge, the compiler to wolf 0.2.21, and the interpreter to lupin 0.1.44.
The lag is still one release; the distance is 91 commits. lupin 0.1.44 re-pinned on the v0.2.20 tag and took every mirror the compiler's newest release had pinned by version, so the ancestry guard answers gap 1, tagged true, 91 commits. /install/ and /play/ carry "one release" and neither carries another phrase.
One program parts, and the pages name it. Every phase: run program in the pinned corpus went through the module this build publishes, four ways, as at ww35: 533 programs, 401 exit, 35 trap, 96 unsupported, 1 fail, 436 candidates, and no instance killed. The fail is rows/eu_bind_empty_row_handled.lu: the interpreter reads a binding's inferred empty row as open and refuses the program where every compiler tier prints 43 42 42 (wolf-interp#176). The compiler's release pins that answer by version and the fix is on the interpreter's trunk, unreleased; the pages say so rather than work around it. Of the other 68 programs the two releases added to the running set, 64 exit 0 and each that names its output prints it, 2 trap as their headers state, and 2 are declines of this build that answer at a terminal (a nested function with a moded parameter; a program in sibling files). Nine of the 64 trap under lupin 0.1.42, the interpreter served until now.
The book's boot script is versioned like every other book script. ww35 left it unversioned because it read the book's root from its own address and a query string emptied it; the book cuts the query off now (bs56), so the whole book asks for its scripts and styles under the book's pin.
Sentences that named the wrong release. The front page leads with what 0.2.21 changed — a match over a fallible value, a ? under a defer refused, a block's errdefer on its own error, and the literal at a bare binding — with 0.2.20's two-phase arguments beside it and 0.2.19's element claims as history. /spec/'s memory-model and types entries gain the clauses those two releases wrote. /install/ dropped two download sizes typed by hand that had gone stale (the Windows archive and the interpreter's executable have both grown since they were written).
ww35 (the site at 0.2.19, the book's contents on lupp.us, and a gap a step narrower) — 2026-09-30
All three gitlinks move: the book to bs55's merge, the compiler to wolf 0.2.19, and the interpreter to lupin 0.1.42.
The book's Contents work here. A click on a sidebar entry from a page one folder down — the front matter, the back matter — landed on a 404: this site's Content-Security-Policy refuses inline scripts, the book's theme set the sidebar's root in one, and the links resolved a folder too deep. The book now loads that script from a file of its own (bs55), and this site serves that book. The policy is unchanged. With this change's nginx config installed, a missing page under /book/ answers the book's own 404 page, sidebar included, instead of the site's, and the book is revalidated on every visit rather than cached for a week. The book's pages also ask for the sidebar's script and the book's styles under the book's pin, so a browser that kept last week's sidebar fetches the new one on the first page it loads.
The security headers reach every file. An add_header inside an nginx location replaces the server's set instead of adding to it, so the site's scripts, stylesheets and the playground's module had been served without any of the four security headers. Every location that sets a header repeats all four now, and a test holds every copy of the policy to the server's, byte for byte.
The lag is one release and the distance is 105 commits. lupin 0.1.42 re-pinned on the v0.2.18 tag and took every mirror that release's gates had pinned by version, so the ancestry guard answers gap 1, tagged true, 105 commits. /install/ and /play/ carry "one release" and neither carries another phrase.
Nothing parts, in verdict or in output. Every phase: run program in the pinned corpus went through the module this build publishes, four ways, as at ww34. At both new pins: 464 programs, 337 exit, 33 trap, 94 unsupported, 0 fail, 370 candidates, and no instance killed. Of the 19 programs the release added to the running set, 18 exit 0 here and print what their headers state, and 1 (a Pool's header read) is declined by name, as at a terminal; three of the 18 trap under lupin 0.1.41, which read a member beside a claim as the whole container. memory/ctl_store_order_nested_index.lu, the one program that parted at ww34, prints what the compiler prints now (wolf-interp#145).
Sentences that named the wrong release. The front page leads with what 0.2.19 changed — two mut claims on two elements of one list, the header reads, and the release-tier crash it fixed in 0.2.18 — and names 0.2.18's paragraphs by their literal. /spec/'s memory-model entry says what this release carries from moves to claims. The parting paragraphs on /install/ and /play/ became v0.2.18's history, naming lupin 0.1.41.
ww34 (the site at 0.2.18, and a gap a step wider) — 2026-09-28
All three gitlinks move: the book to bs54's head, the compiler to wolf 0.2.18, and the interpreter to lupin 0.1.41.
The lag is two releases and the distance is 152 commits. lupin 0.1.41 is still built to the v0.2.16 tag commit, and the compiler has tagged twice since, so the ancestry guard answers gap 2, tagged true, 152 commits. The interpreter did release beside the compiler, but it took the read half of the new element clause without re-pinning, which is why the gap widened. The pinned book's changelog says the same pair from the other end. /install/ and /play/ carry "two releases" and neither carries another phrase.
One program parts, in its output and not in its verdict. Every phase: run program in the pinned corpus went through the module this build publishes, four ways, as at ww33. At both new pins: 445 programs, 319 exit, 33 trap, 93 unsupported, 0 fail, 352 candidates, and no instance killed. Of the 23 programs the release added to the running set, 22 exit 0 here and 1 (a revival through a Pool handle) is declined by name, as at a terminal. Their output was compared with their headers too, and one differs: memory/ctl_store_order_nested_index.lu, where this interpreter calls every index but the last twice (wolf-interp#145, open). The only answer any walk moved was memory/list_session_struct.lu: its old text reads an element it moved, and this interpreter is the first to trap on it; the release's new text copies the element and exits 0 in both places.
Sentences that named the wrong release. The front page leads with the two silent wrong answers 0.2.18 refuses (#460, #464), then element places and the store order, and names 0.2.17's paragraphs by their literal. /spec/'s four 0.2.17 lines move to v0.2.17, and the memory-model entry says what this release rules. Relative references that had moved under the pages — "the pin before this one", "two pins back", "until this bump" — name their release now. Three unstamped sentences had gone stale with nothing to catch them: /spec/'s "every release since has widened" the grammar (v0.2.16 and v0.2.18 left it byte-identical), /spec/'s "the interpreter this site pins is the first" to serve a climbing path (that was lupin 0.1.40), and /play/'s "a week ago" about the menu's range program, which has run here since wolf-interp#83 closed.
ww33 (the site at 0.2.17, and a gap that costs nothing) — 2026-09-26
All three gitlinks move again: the book to bs53's head, the compiler to wolf 0.2.17, and the interpreter to lupin 0.1.40.
The lag is one release for the second bump running, and the distance is 72 commits. lupin 0.1.40 is built to the v0.2.16 tag commit, so the ancestry guard answers gap 1, tagged true, and the distance shrinks from 148 to 72. The pinned book's changelog, written from the other end, says the same: 72 commits and one release. The planning row that launched this bump said the lag would be zero; the guard said one, and the pages say what the guard counted.
The parting is none, across a gap that is not none, for the first time. Every phase: run program in the pinned corpus went through the module this build publishes, four ways, as at ww32. At both new pins: 422 programs, 297 exit, 33 trap, 92 unsupported, 0 fail, 330 candidates, and no instance killed. The corpus's move changed the class of no program present at both pins, under either module. The module's move changed three: the one parting ww32 named, corpus/conc/proc_join_param.lu, now knows the name Proc and reaches the proc tier, which a browser build declines, so it went from a refusal to a decline; and the list and Map index stores that 0.2.17 brought in, which the module this site served until this bump trapped as a use after move, exit 0 under this one with the output their headers state. Of the 11 programs the release added to the running set, 10 run here and print their headers' output, and the eleventh is a store through a Pool handle that the interpreter declines by name at a terminal too. The prediction committed before the first pin moved called every count in that table exactly.
Sentences that named the wrong release, found while re-reading the clocks. The front page's release paragraphs told 0.2.16's story and warned about a defect 0.2.17 fixes; they tell this release's now, with the two before it named by their literals. /spec/'s six release-bound lines move to v0.2.16, and four new ones say what 0.2.17 moved in the grammar, the memory model, the protocol and the operating-system document. Four sentences carried no stamp and had gone stale with nothing to catch them: /spec/'s "this release" over the format-spec production, which is v0.2.15's; /install/'s pass-verdict shape, which is v0.2.16's; /install/'s specification pin "two tags" behind the compiler, false since the gap closed to one; and /play/'s net paragraph, which called v0.2.14's writev witness "this release's" and said the interpreter did not have the call, which it has since 0.1.37. Two /spec/ sentences that an earlier insert had split mid-clause are whole again.
The doors agree again. Read live at this bump, the tap and all four AUR packages carry the pair this site serves, so the paragraph that said the AUR trailed by two tags is gone, and the two literals it named left the version allowlist with it.
What the pages had to re-read: 56 clocked allowlist lines — 32 version literals, 6 stamp entries and 18 counts, the number predicted. Two entries left and six arrived for the literals the rewrites name.
The book. All four readings /reading/ makes were derived at the checkout and none moved; the table of contents is byte-identical across the bump. The web edition is 48 pages at both pins and the print edition is 5.6 MB.
The build, measured. 63 pages dry, and every one of the 63 already answers 200 on the served site, so the bump adds no URL and removes none. The menu is 36 programs, each in the class the page claims and none carrying a note. The stamps: 33 book chapters, 142 diagnostics, 34 warnings, 18 net programs, 36 samples, 72 spec commits. The grammar is not byte-identical this time — the moded index store is a new production — and the anchor set is diffed both ways: two in, none out.
ww32 (the site at 0.2.16, and the pin it could take) — 2026-09-24
All three gitlinks move: the book to bs51's head, the compiler to wolf 0.2.16, and the interpreter to lupin 0.1.38 — the pin ww31 measured, refused and said would come. It is the first bump at which all three clocks move at once.
The lag is one release, and the number is the one the book's colophon prints. lupin 0.1.38 re-pins on the released line: the specification checkout it records is the v0.2.15 tag commit itself, so the ancestry guard that stopped ww31 answers yes reflexively, and the distance along one line exists again. The build counts it at 148 commits, one release, and the pinned book — written from the other end, against the same two repositories — says 148 commits, one release, in its colophon. One gap, two routes, one number, for the second time this file has been able to say so.
The parting goes from twenty-two programs to one, and the one is a type name. corpus/conc/proc_join_param.lu writes Proc as the type of a parameter, which 0.2.16 is the first release to accept — Scope and Proc[T] elaborate in a signature now, where the capitalised names were unknown and the lowercase keywords refused — and the interpreter answers E0301, nothing named Proc in scope. Both machines agree about what a proc is and part over whether the word may stand there yet.
The census was measured four ways, because two things moved at once and a single walk cannot say which did what. Both corpora went through both modules. At the deployed pins — the corpus the site serves today and the module it publishes today — 387 phase: run programs, 252 exit, 31 trap, 81 unsupported, 22 fail, and one that killed the wasm instance outright. Holding the corpus still and moving only the module: 271 exit, 32 trap, 84 unsupported, zero fail, zero dead. Holding the module still and moving only the corpus: 27 fail. At both new pins: 411 programs, 287 exit, 33 trap, 90 unsupported, one fail, candidates 320. The row-level reading is the point of doing it four ways — no program present at both corpus pins changed its class when only the corpus moved, so every class change at this bump is the interpreter catching up, and the twenty-four programs that arrive are the corpus's alone: sixteen exit, six unsupported, one trap, one fail.
Twenty-two of the twenty-six rows the module's move touched are the closing list, and four are not: three programs that had been declining for want of a builtin now run, and the dead one answers. The prediction committed before the first pin moved called the program count, the distance, the allowlist cost and the dead program right, and got the parting wrong in both directions: it said two programs would part and named the two pool witnesses, and the pool witnesses report a std-subset decline rather than a refusal, while the row that does part is one the prediction had reasoned about under the other handle's name and then written off.
Six programs stopped being refused and started being declined, which is a distinction this site had never had to draw. The method-surface witnesses were fail(E0201) with the dot unparsed; this interpreter parses the dot, and what it then has to do is reach the receiver's home module, because a method call is the function it is. A browser tab has no standard library on disk to reach, so they answer unsupported with a reason naming the std root. It is a decline of this build rather than a refusal of this interpreter, and to a reader the two words look alike. /play/ spells it out.
The program that was worse than a refusal is fixed. corpus/grammar/range_header_inclusive_max.lu reached the old module's own unreachable and took the instance with it, so a visitor who pasted it got a dead page. The interpreter walks the range now instead of materializing it. Fed to the new module on purpose — nothing on this site had ever fed it, and the menu check only feeds the menu — it exits 0 and prints its two lines, and the wide-range witness beside it does the same. The paragraph that warned about it is gone, and the one that records the fix is in its place.
What the pages had to re-read: sixty-one clocked allowlist lines, which is what all three clocks moving costs — thirty-four version literals, six stamp entries and twenty-one counts. Nine entries left with the sentences they counted and four arrived. Four sentences were false under the new stamps and all four were the same mistake this file has caught before, a paragraph telling the last release's story under this release's number: /spec/'s memory, concurrency and operating-system status lines, and the front page's release paragraph. They are rewritten, and the release they described is a literal now rather than a stamp.
And the doors part for the first time. The freshness claim is read live at every bump and both channels have agreed at every bump until this one. The tap builds both new tags. The AUR's four packages trail their upstream by two, the compiler's pair and the interpreter's pair alike. That is a door behind its upstream rather than a door ahead of the page, which is the direction a reader needs told, so the sentence says which channel is which and what installing from the trailing one gets you.
The book. All four readings /reading/ makes were derived at the checkout and none moved: thirty-three chapters in the table of contents, the same two chapters holding a whole page back, chapter 21 still three of its five sections written, and part 5 still six programs and a coda. The web edition is 48 pages at both pins and the print edition grew to 5.6 MB. The paragraph ww31 added — that the book's pair was not this site's — comes off, because the colophon now names exactly the pair this site serves.
The build, measured. 63 pages dry, 63 live, and every one of the 63 the dry build writes already answers 200 on the served site, so the bump adds no URL and removes none. The menu is 36 programs, each in the class the page claims for it and none carrying a note. The stamps: 33 book chapters, 142 diagnostics, 34 warnings, 18 net programs, 36 samples, 148 spec commits. The grammar is byte-identical across the compiler bump and the anchor set is diffed both ways: fifteen in, none out.
ww31 (the site at 0.2.15, and the pin it could not take) — 2026-09-17
Two of the three gitlinks move. The book takes bcad859 (bs50) and the compiler takes 2e4ca76, which is wolf 0.2.15. The interpreter does not move, and that is this entry's subject.
lupin 0.1.37 cannot be pinned here, and the check that says so is right. The interpreter records the specification checkout it was built to as a gitlink, and 0.1.37's is a compiler revision the released compiler's history does not contain: a branch head that the rebase-merge landing it rewrote under other shas. wolf-lang preserves it as a tag so a clone can still fetch it, and that is exactly why the failure is the one worth having — scripts/check-lag-phrases.py gets past its "is this a commit here" guard and stops on the next one, "the compiler this site pins does not descend from it". The lag this site publishes is a distance along one line, and there is no such distance to publish for that pin. Had the gate not been there, scripts/stamp-counts.py would have stamped the distance anyway: rev-list answers a number for two commits on divergent lines, and the number is two hundred and thirty-four commits of rewritten history. It would have reached both pages and looked like every other measurement on them. The site serves 0.1.36 until the interpreter re-pins on the compiler's line.
So the gap is three releases, and the widest it has been. 0.1.36 was built at the v0.2.12 tag, and 0.2.15 is three releases past it. Both pages carry three releases and neither carries another phrase, and both now say the thing this site has never had to say before: the compiler's second line names an interpreter the playground does not run.
The parting goes from fifteen programs to twenty-two, and the module is the control. Every phase: run program in the pinned corpus went through the wasm module this build publishes, first against the corpus at the DEPLOYED compiler pin — 361 programs, 242 exit, 31 trap, 73 unsupported, fifteen fail, which is the fifteen the served pages state, file for file — and then against 0.2.15's: 385 programs, 250 exit, 31 trap, 81 unsupported, twenty-two fail. The module is byte-identical between the two runs and no file present at both pins changed verdict, which is what makes the seven that joined the corpus moving rather than the harness. Six are s166's method surface, including the book's own word counter in both its forms, and the seventh is one more range type. All of them stop at a dot or a type name; none is a ruling.
One program does not part, it kills the tab. corpus/grammar/range_header_inclusive_max.lu reaches the module's own unreachable and takes the instance with it, so a visitor who pastes it gets a dead page rather than a refusal. It is not on the menu, check-samples.mjs only feeds the menu, and nothing on this site had ever fed it. The compiler's own release notes describe the same file exhausting a much larger machine against a later interpreter build, so this is that defect in a sandbox with no memory to spare. /play/ says so in prose and names the file; the fix is the interpreter's.
The specification distance crossed a hundred and had no word for itself. stamp-counts.py spelled counts to 99 and its docstring said the pages used digits beyond that. Nothing did: check-lag-phrases.py requires the distance to be the six hundred and eleven stamp and refuses any other spelling, so the first count over a hundred was a build failure, not a page in digits. It is two hundred and thirty-four now. The speller reaches 999 and still refuses what it cannot say, which was checked in both directions before it was trusted.
What the pages had to re-read. Forty clocked allowlist lines reddened — twenty-four version literals, four stamp entries and twelve counts — which is what a wolf-and-book bump with the lupin clock standing still costs, and it was the number predicted before the pins moved. The front page's release paragraph was rewritten: the advertised release is the one that changes what an already-written program means, push copying its element where it used to keep the caller's handle alive, and the string runtime becomes a literal behind it. /spec/ moved two release-bound sentences onto v0.2.14 and wrote the five documents 0.2.15 grew. Three readings on /reading/ reddened on the book clock and all three stood, derived at the checkout: thirty-one of the chapters written through, chapter 21 three of its five sections, part 5 six programs and a coda. §13.1 retires a clause of this file's own ch13 correction, which said §13.1 was vacant.
The build, measured. 63 pages dry, 63 live, and every one of the 63 the dry build writes already answers 200 on the served site, so the bump adds no URL and removes none. The book is 48 pages and the PDF 5.5 MB at both pins — SUMMARY.md is not in the book diff and no book file is added or removed. The menu is 36 programs. The stamps: 33 book chapters, 142 diagnostics, 34 warnings, 18 net programs, 36 samples, 234 spec commits.
ww30 (the book at 0.2.14) — 2026-09-15
The book's gitlink takes b3cb0db, bs49's trunk: the book pinned to wolf 0.2.14. The compiler and the interpreter do not move, so the lag stays two releases and seventy-two commits, and the served check holds it as a regression.
§3.2 names the range type and §6.1 names a row once. range[int] and range[char] are written down in §3.2 with their start and end, and §6.1 teaches error-set aliases beside the row they name, as a program only the compiler runs; lupin 0.1.36 does not parse the item yet, and the chapter says so. Chapters 26 and 30 run their bare fences on both machines now, chapter 5 takes list literals where the lesson is construction, and §8.6 and §15.3 fix their strings.
The two gaps agree again. At ww29 the colophon measured forty-three commits between its pair and this site measured seventy-two between its own. The book re-pins at wolf 0.2.14 and lupin 0.1.36 now, the pair this site advertises, and the colophon says seventy-two: one gap, two routes, one number.
The book's census, by phase: header, did not move — 210 run, 18 typecheck, 7 parse, 6 mem, 3 resolve at both pins. No book file was added or removed and SUMMARY.md is not in the diff, so the web edition is still 48 pages and the PDF still 5.5 MB. All three readings on /reading/ reddened and all three stood, derived at the checkout: thirty-one of the chapters written through, chapter 21 three of its five sections, part 5 six programs and a coda.
ww29 (bump 3, the book) — 2026-09-14
The book's gitlink takes 119b269 — bs45, bs46, bs47 and the one-line fix the maintainer asked for. The compiler and the interpreter do not move, so the lag stays where the bump before left it.
§5.6 renders the Num alias as one code span again. The source line trait Num = Add + Sub + Mul + Div + Rem + Eq + Ord had wrapped after Mul, so its continuation began + Div, which Markdown reads as a list item: on the served page the alias was prose followed by a bullet. The line is one line now, and the served check asserts the alias inside one <code> and no list item beginning with Div — both directions, since a check that only counted the bullet away would pass a page that lost the alias too.
The book's census, by phase: header, did not move. The exercise programs under principles/exercises carry the same headers at both pins — 210 run, 18 typecheck, 7 parse, 6 mem, 3 resolve — so what bs45's index, bs46's ch15 graduations and bs47's walks moved is prose, solutions and the index, not the population. Diffing the headers rather than the file list is ww28's lesson, and here it says the opposite of what a file list would: 57 files changed and zero rows.
The book measures a different gap from this site now, and both are right. The colophon re-pins at wolf 0.2.13 and lupin 0.1.36 and states forty-three commits between its pair; this site advertises wolf 0.2.14 and stamps seventy-two between its own two gitlinks. At the last book bump the two repositories measured one gap by two routes and agreed; at this one they measure two gaps, v0.2.12 to v0.2.13 and v0.2.12 to v0.2.14, and the served check holds each number on its own page and off the other's.
All three readings on /reading/ reddened and all three stood, derived at the checkout: the colophon still reserves two chapters and five sections, chapter 21 still has three of its five sections written, part 5 is still six programs and a coda, and the chapter total is thirty-three. The book's own count of sentences saying the interpreter has no filesystem came down with bs46; this site's three had already gone at the interpreter's bump.
ww29 (bump 2, the compiler) — 2026-09-14
The compiler's gitlink takes wolf v0.2.14, the string runtime — and, because this site skipped a release, v0.2.13, the literals, with it. The interpreter does not move, so the gap that closed at the bump before reopens, to the widest it has been with both ends at tags: lupin 0.1.36 was built to the v0.2.12 tag, two releases and seventy-two commits behind what this site now advertises. Both pages say so, the distance stamp is back on both, and for once the compiler's own --version agrees: 0.2.14 declares 0.1.36, so the pairing is level while the pin is not.
The gap costs fifteen programs, and every one of them is a spelling. 363 phase: run programs at v0.2.14 go through the module this build publishes — 25 more than at v0.2.12, none of them promoted, by phase: header diff — and fifteen answer a refusal where the compiler runs the program: the six list-literal witnesses and the nullary-variant pattern at E0201, the range-type witnesses at E0301, three error-alias witnesses at E0201. Every parting this site recorded before was a ruling the interpreter had not mirrored yet; these are syntax its parser predates, and it stops at the first bracket, type name or keyword. Nothing else moves: the other running programs the two releases added exit 0 in the tab and print what the compiler prints, byte for byte, and no program that ran at the last pin has stopped. Census: 244 exit, 31 trap, 15 fail, 73 unsupported, 15 class partings, 0 wrong answers.
Predicted 18 partings, measured 15. Three programs read as new spellings were not: rows/error_alias_ident.lu uses the alias as a plain name, strings/end_relative_get.lu's ^ was already in the interpreter, and memory/list_elem_copy_loop.lu's bracket is an index. A grep for a construct is not a parse, and every miss was in the direction of predicting a refusal the tab did not make.
Five sentences on /spec/ told v0.2.11's story with no stamp and no literal — "this release widens it", "this release adds [mem.str.imm]", "at this release [conc.chan.close]", "the document this release grows most", and "this release does not touch the document", the last one false at this pin because 11-os.md gains [os.net.writev.head]. The stamp allowlist's own header says it cannot see a bound sentence that carries no placeholder; these carried none. They name their releases now, as literals on the compiler's clock or as stamps counted as bound, so the next bump re-reads them.
The front page tells two releases' stories under one stamp, and says so: the literals are v0.2.13's by name and the string runtime is the stamped release's. The doors, read live: the tap at v0.2.14 and 0.1.36, the AUR at 0.2.14-1 and 0.1.36-1 — level with the page on both clocks, so "every channel agreed" is back on /install/, with the record that at the pin before this one the compiler's doors were ahead of the page.
Stamps that moved: the diagnostic catalogue 140 → 142, the net programs seventeen → eighteen (writev_head_gather.lu, which the tab declines at the listener), and the distance. Warnings, the chapter total and the menu did not. The anchor register the ahead gate reads grew 475 → 498. docs/platforms.md is byte-identical at the two pins, so the thirty-six rows stand, and the toolchain pin is still 1.97.1.
ww29 (bump 1, the interpreter) — 2026-09-14
The interpreter's gitlink takes lupin 0.1.36, the filesystem tier, and the counted gap closes to zero for the third time: 0.1.36 was built to the v0.2.12 tag, which is the commit this site's compiler gitlink names. Both pages say the same commit and neither states a distance. The compiler and the book do not move.
"0.1.36 pairs with 0.2.14" is the compiler's sentence, not the interpreter's. wolf 0.2.14's changelog names 0.1.36 as its pairing; 0.1.36's own gitlink is v0.2.12, and so was 0.1.35's. The lag rule counts by the interpreter's gitlink, so this bump is accepted at a gap of zero, and the order interpreter → compiler → book reads 1 → 0 → 2, each a phrase the closed set has. Compiler first would have read 3 and been accepted too: the rule reds only on a pin the site's compiler does not descend from, and no order of these bumps produces one.
The interpreter has a filesystem, and this tab still does not. The book found sixteen sentences saying the reference interpreter opens no files; this site had three live ones — /play/'s tier paragraph, and the twin sentence on /play/ and /install/ that called fs_fstat "declined by design in every build of it" — plus one frozen in this changelog, which stands as history. The three are rewritten: the filesystem joins the tiers the WebAssembly build declines, for the tab's reason and not the interpreter's, so /play/ counts eight tiers where it counted seven, and fs_fstat is named as a program that answers under lupin at a terminal and not here. The wasm arm's own words are the measurement. Every one of the nine phase: run programs that touch the tier — the same nine that 0.1.36's release notes name as its native movers — answers unsupported with "this wasm build has no filesystem to open" where it answered "this machine has no filesystem by design", or "does not resolve" for a name the old build did not carry.
Census: 338 phase: run programs through the module this build publishes, and no class moved. Predicted and then measured: 237 exit, 31 trap, 0 fail, 70 unsupported, 0 class partings against the programs' own headers and 0 flips against the last census. What moved is one reason string on nine rows, and one witness outside the census: typecheck/interp_spec_on_union.lu answered unsupported at the last pin and answers E0413 now, because 0.1.35 mirrored it. The parting /play/ recorded is closed from the interpreter's side, and the page says so rather than deleting the sentence.
The compiler's doors are ahead of the page, and the page says so. Read live before the bump: the tap's wolf formula and both AUR compiler packages carry 0.2.14 while this page advertises 0.2.12 until the next bump; the tap's lupin and both AUR interpreter packages carry 0.1.36, level with the page. "Every channel agreed" is gone from /install/, and the wolf --version transcript there names lupin 0.1.34 as a literal on the compiler's clock, because that is what 0.2.12 prints — the compiler's memory of the interpreter it was cut beside, not a claim about the one this page serves — with a sentence under it saying which is which. The gap in that direction, the compiler declaring an interpreter release behind the one served, is one the lag rule does not measure; the literal and its clock are what hold it.
The lag-phrase suite carried a fossil of its own. Its witness planted ww27's paragraph with the phrase for a gap of zero typed in. That held at every pin where the gap was one and went red the first time the suite ran at a gap of zero, because the check correctly accepted a phrase the page now owns. The witness quotes the phrase for a gap the measurement does not own, read off the script's own table, so it cannot go stale the same way twice.
Two numbers moved: the tier count, and the module grew from 971 to 976 KiB raw. Every entry on the interpreter's clock was re-read at the checkout, and the five historical literals on /play/ stand.
ww28 (item 0, the second book bump) — 2026-09-12
The book's gitlink takes bs44. The book re-pins itself at wolf 0.2.12 and lupin 0.1.34, both from the release archives by digest, so its --version transcripts, its two-clock pair and its own audit clocks all moved — none of which is a number this site counts, which is the useful thing about a clock that reddens anyway.
The chapter-5 exercise that lupin could not run has stopped being one: the eight per-machine sentences in chapters 5 and 12 graduate to programs both machines execute, now that the interpreter mirrors how a Map answers and how an operator reaches a trait, and the compiler stopped declining §12.3's worklist. §6.5's wordcount tallies with §5.1's one-liner, because the absent key answers a row on both sides now and the first-time branch had nothing left to do. §15.2's supervisor builds the child's message at the spawn site, since a string a proc builds dies with the proc's region.
All four numbers on /reading/ reddened and all four stood, which makes four bumps running. They were derived at the checkout rather than reasoned from the diff, and the reason to say so this time is chapter 5: it IS in the diff, and it is the chapter this page quotes by title. Its heading and all six section headings are unchanged and its exercise 5-7 is still the grade book, so the page needs no edit — but that was measured, not assumed. The table of contents, chapter 21, chapter 25, chapter 29 and every chapter of part 5 are absent from the diff entirely.
What the site follows is the colophon, which is the outside view of the written-through figure: its printing reads the two releases now rather than a release and a development build, and the distance it states between the pair is the same thirty-one commits this site stamps between its own two gitlinks — two repositories measuring one gap by different routes and agreeing.
ww28 (items 1 and 2) — 2026-09-12
No pin moves. One gate changes address, and it is the last prose rule on this site that reddened somewhere its author could not see.
The lag-phrase rule is a script now. /install/ and /play/ state how far the interpreter's specification pin sits behind the compiler this site advertises, in a closed set of four phrases, and a page must carry the one for the counted gap and none of the other three. That rule is correct, it is page-wide by design — a lag paragraph left unrewritten is exactly what it catches, and no test can tell a quotation from a claim — and until today it lived in PowerShell, inside one step of the Windows workflow, on a runner nobody runs locally, in a job the linux workflow does not depend on.
Every other prose rule here — the version literals, the number words, the revisions, the placeholder sweep — is a script that runs in the build and therefore reds on the author's machine at the moment the sentence is written. This one is now the same shape: scripts/check-lag-phrases.py, called from build.sh beside the other two audits, deriving the gap by ancestry over the two gitlinks exactly as the Windows step did. The Windows step remains, and calls the script rather than carrying a second copy of the rule; its summary table is built from the script's own measurements, so the table and the refusal can no longer disagree about the same gap.
The witness is the paragraph that made the case. A gate nobody has watched fail is a claim, so the suite plants the sentence that caused all this — a /play/ paragraph that quoted the phrase for a gap of zero while the gap was one, and reads correctly to a person — and requires the check to refuse it. Five more cases go with it: a page that drops the phrase it owes, the revision written by hand where the stamp belongs even when what is written is correct, and the distance stamp required exactly when there is a distance, which is one test with two branches because the right answer inverts at a gap of zero. Nothing in the suite spells a phrase or a distance as a constant; it asks the check what it measured, because the answers move at every bump and a test with one release typed into it would be the same fossil the check exists to refuse.
What it has already caught. At the two bumps before this entry, run from a scratch copy rather than from the tree, it refused four passes of a gap-one-to-gap-zero rewrite: a new paragraph that said one release ago, an older one that said a parting lasted exactly one release, a third that said it closed in one release, and the comment written to explain the third, which quoted the phrase while explaining why it could not be there. A comment is served prose. At ww27's arrangement those were four separate round trips through a queue on another operating system.
Two sentences that state the same relation in words the rule does not reserve were wrong at those bumps and held by nothing. The rule is deliberately a short closed set — widening it to arbitrary phrasings is not something a machine can apply — so both sentences carry their own pin now and are re-read by hand, and the page says so about itself where a reader can see it.
ww28 (item 1, the third bump) — 2026-09-12
The compiler's gitlink takes wolf v0.2.12, the papercuts release. The interpreter does not move, so the gap that closed a few hours ago reopens: lupin 0.1.34 was built to the v0.2.11 tag, which is one release and thirty-one commits behind what this site now advertises. Both pages say so, and the distance stamp is back on both.
A gap that costs nothing, which has not happened before. Every one of the pinned corpus's phase: run programs goes through the module this build publishes, and not one of them answers in a different class from what its own header declares — with the gap open. Every previous open gap this site has served cost something a reader could hit. This one does not, because the release brings programs the interpreter already answers: a lint about arithmetic beside an else, a unit-typed tail discarded rather than returned, the float remainder, a Map written through a generic, and a struct literal in parentheses promoted from a static witness to a running one. The one new running program the tab declines is a module spread across sibling files, and the editor here is one buffer — this page's limit, not the interpreter's vintage, and no release of anything will change it.
One of the release's new negatives parts in *which* refusal rather than in whether: formatting an error union with a width is a typing refusal for the compiler and an unsupported here, because the interpreter's format-spec grammar speaks about strings. Both machines refuse the program. It is a resolve-phase witness, so it sits outside the running census, and it is written down because a parting recorded only when convenient is not a record.
The front page stopped claiming this release brought what the last one did. Three showcases stand at the top of that page — the switch with its range arms, the assignment through a string slice that is now named rather than blamed on the backend, the send in a loop body as a warning — and one of them was introduced with the words "at v*this release*", stamped from the pin. They came in at the release before. A stamp cannot fossilize, which is the whole reason it is a stamp; a sentence built around one can be true of exactly the release it names and false the moment the stamp moves, with nothing to notice. The three are stated as the language now, and what this release actually moved has its own paragraph beside them.
Two numbers moved and were re-measured rather than carried: the warning catalogue gains an entry, and the counted distance is back. The diagnostic catalogue, the chapter total, the net programs and the sample menu did not move. Every entry on the compiler's clock was re-read at the checkout — docs/platforms.md is byte-identical at the two pins, so the rows the native lane does not execute stand, and so do the refusals the Windows page counts.
ww28 (item 1, the second bump) — 2026-09-12
The interpreter's gitlink takes lupin 0.1.34, and the distance the two pages have stated at every pin for a month is gone: the specification revision this interpreter was built to IS the commit the compiler this site advertises was tagged at. Not the tag before it, not a revision of trunk — the same commit. Both pages say so, and neither states a distance, because there is none.
The five that parted all run. The release this site advertises ruled how a Map answers and how an operator reaches a trait; the interpreter at the last pin had been built before either text existed, and five corpus programs parted on it — counting words with a Map alone, integer keys, + and < reaching an impl, and the chapter-5 total under a bound, which was refused outright. The census measured after the bump: all five exit 0, every one of the new Map witnesses runs where three did, and across the whole pinned corpus there is no program whose verdict class differs from what its own header declares. The nine running witnesses of the one-line if are unmoved, and so are the seven tiers — a page has no sockets whatever the pins say, and that was never what the distance was about.
The prediction and the measurement agreed on every row, which is worth recording because the interesting part was elsewhere.
Four reds, on prose nothing else would have caught. Rewriting two lag paragraphs from a gap of one to a gap of zero took four passes, and every one of them was refused by the lag-phrase rule for the same reason: the four phrases the two pages reserve are ordinary English, and prose written about a closing gap reaches for them without meaning them. The new paragraph said "one release ago the compiler ruled"; an older paragraph said a parting "lasted exactly one release"; a third said it closed "in one release"; and the comment written to explain that third one quoted the phrase while explaining why it could not be there. A comment is served prose. That rule has lived in PowerShell on one runner since ww13, which means four separate round trips through a CI queue if it had not been run here — and it is exactly the case this lane's remaining items move under scripts/.
Two sentences that state the same relation in words the rule does not reserve were wrong and held by nothing: lupin --version's paragraph called the pin "the tag before this compiler's", and the packaging paragraph dated the one time the doors disagreed to "the page it replaces" rather than to the pin it happened at. Both are re-stated without a tense that a later bump falsifies. The doors themselves were read live: the tap builds the compiler's tag and the interpreter's, and both AUR compiler packages and both interpreter packages are level with the pins.
ww28 (item 0) — 2026-09-11
The book's gitlink takes bs43, a27b684 to 27a2379, and the pin it carries is a release pin in all three printings: wolf 0.2.11 and lupin 0.1.33 by tag rather than by development revision.
Chapter 5's exercise 5-7 was an RPN evaluator, and the thing that makes an RPN evaluator worth writing is the ? on a pop that can fail — which chapter 5 has not taught. It moves to chapter 6 as exercise 6-14, where that is the point, and a new self-contained 5-7 takes the slot it vacated: a grade book over tuples, the chapter's own best called with a comparison function, and an empty block guarded before the indexing trap. §5.6 is now the whole of generics in one section — a bound that orders, a bound that negates, and a body under two of them at once. The one-line if is taught in the chapter that teaches if. Eighty-seven bare sends across ten chapters are spelled with the operator that answers them.
All four numbers on /reading/'s book clock reddened and all four stood, which makes four bumps running. The diff does not touch the table of contents, and does not touch any of the three chapters the held set names, so the re-read was short — but it was read at the checkout, not reasoned from the diff: thirty-three chapters in the table of contents, thirty-one written through, chapter 21's five sections with two held, and part 5's six programs and a coda.
One paragraph on /reading/ is rewritten, and it is worth naming because nothing in the diff caused it. The page carries a caution that a stable anchor is not a stable subject, and it was written at the bump that rearranged chapter 5, in the wording "at this pin one shipped anchor changed meaning" and "if you bookmarked it before today". The anchor did not move again at this bump — so one bump after it was written, both of those clauses were false and the caution under them was still exactly right. It is stated without a pin now. That is the same defect as a lag paragraph left unrewritten, in prose nothing measures: a sentence that was true of the pin it was written at and is a claim about every pin after it.
ww27 — 2026-09-11
No pin moves. Three gates close, and all three are the same shape: a claim this site published that nothing here could have checked.
The changelog comes under the version audit. Since ww25 this file has been a second root for the count audit, so a number word in an entry costs an allowlist line. Version literals stayed outside it, and there are a great many of them — thirty-eight distinct spellings across 173 mentions, every one of them history. They stayed outside for a reason: the allowlist's entry grammar made the clock mandatory, so admitting the file would have put all of them on the compiler's clock or the interpreter's and reddened every one at every release, for prose that cannot rot. A release tag named in a dated entry is a fact about that entry.
So the grammar admits frozen in the clock's place, which is the idea the count allowlist has carried since ww25 (counted=0) spelled for literals: a version no pin can move, carrying no clock, with the re-read branch skipped. It is admitted only for a path named on the command line as an extra root — a site page is undated and living, a changelog entry is dated and closed — so writing frozen on a site entry is an allowlist error rather than a quieter audit. The version audit went from 31 mentions in 22 entries to 204 in 60, and the number of entries a release re-reads did not change. What the new lines buy is not a re-read; it is that writing a version into a served sentence costs one allowlist line, everywhere, which is the moment its author has to check it.
The book's chapter total becomes a stamp. /reading/ states four numbers on the book's pin and exactly one of them has no judgement in it: the chapter total is the count of chapter entries in the book's own table of contents and nothing else. At the last book bump all four reddened and two had moved, that one among them. It is measured from the pinned checkout at build time now, by the same script that stamps the diagnostic and warning counts, off the same pin the audit's book clock reads (wolf-web#28). The page is byte-identical apart from the comment that explains itself.
The other three stay literals, and the page now says why beside them. The written-through figure is not a field the book publishes — this site derives it as the total minus the chapters that hold a whole page back, and it decides what holding back means; the book's README counts it one way and its colophon another. Chapter 21's section figure and part 5's program figure are stated inside sentences about which sections and chapters are reserved, and the reservation is prose. Three readings of one book: stamping any of them would publish a number the source does not agree with.
The tab may not run what the advertised release does not carry. The counted pin lag compares the specification revisions two machines were built to read. That is not a statement that they answer alike, and at the last pin the difference reached a reader for the first time: the gap was zero, both pages said *the same commit*, and the playground ran a one-line if the advertised compiler refused (wolf-web#35). The interpreter mirrors a clause ahead of its pin on purpose and says so in its own release notes; nothing here read those notes. A reader could copy a program out of the tab and watch their compiler decline it.
The build now runs a small set of witnesses through the module it is about to publish and requires every one that runs to name an anchor the pinned release's own specification declares. A witness the module refuses is not a failure — it means the tab is not ahead on that clause, which is the ordinary state — so an entry is never retired for going quiet. The first witness is that one-line if, and it is green, because the compiler took the spelling at its last release: exactly the position in which a gate is a claim rather than a measurement. So the suite beside it plants a witness that runs against a clause no release has ever declared and holds the refusal.
ww26 (item 2) — 2026-09-11
The compiler's gitlink takes wolf v0.2.11 and the book's takes bs42, in one bump. The counted gap reopens to one release, and this time the interpreter's pin is a release tag that is simply the tag before this one, ninety-one commits back — a shape neither of the last two bumps had. A pin being a tag and a pin being current are different claims; this is a pin where the first holds and the second does not.
The parting that pointed the other way lasted exactly one release. Yesterday this site reported something it had never had to report: the interpreter took a one-line if that the compiler refused, because the spelling landed on the compiler's trunk after its release was tagged. v0.2.11 is the compiler taking it. All nine running witnesses of that ruling now agree in both places and the three refusals are refused on both sides. The record of it stays on the pages, because a disagreement recorded only while it is open leaves a reader no way to check that it ever ended.
And the parting is back in its usual direction, on a program a reader is unusually likely to try. This release rules how a Map answers and how an operator reaches a trait, and the interpreter was built before either text existed. So counting words with a Map alone — the program chapter 5 of the book is now built around — runs under the compiler and does not run in the playground. Four others go with it. Three of the five new Map witnesses do run there, so the mirror is partial rather than absent, which is the useful thing to know and the thing a verdict count alone would hide.
Four of those five refusals wear a misleading word. They report unsupported, the same verdict the tab gives a socket call, and they are not the same kind of thing: a tier refusal is permanent and by design, while "+ is not defined on Money and Money" is this interpreter being honestly behind a clause. The first never changes here. The second changes at the interpreter's next release. Both pages now say so.
The census moved for the first time in three bumps: 308 phase: run programs to 332, all twenty-four additions accounted for by name, and no program that ran before this release stopped. The sample menu did not move; it is curated, not merely every running program. The stamped counts that ride the compiler moved with it and the ones that do not, did not.
On the book: all four counts stand, a fourth bump running. One shipped anchor changed meaning, which is worse than one breaking. ch05.html#5.5 was the trait section and is the struct section now, with traits at #5.6, because chapter 5 was rearranged to put the struct before the trait that operates on it. Nothing 404s. An old bookmark to §5.5 still resolves, still renders, and quietly shows a different subject, so if you saved the traits section of chapter 5 before today, the link you want is #5.6. Chapter 7 also gained #7.8, which takes a free number and moves nothing. /reading/ now says that stable means the number keeps pointing at a page, not that it keeps pointing at the same subject — the promise the page makes is narrower than it read.
The packaging doors were read live and, for the first time in two bumps, all four agreed; the entry before this one had to report one door ahead and one behind. Their versions are stamps now rather than literals, because what they publish is the release this page advertises.
ww26 (item 1) — 2026-09-11
The interpreter's gitlink takes lupin 0.1.33, and the counted gap between the two implementations closes to zero. The tag names 662b14c as the specification it was built to, and 662b14c is this site's own compiler gitlink and the v0.2.10 tag — so the pin is not merely close to the release this site advertises, it is that release. The zero sentence returns to both pages, the distance stamp comes off them, and the stamped revision is for the first time in three bumps the same seven characters as the compiler pin beside it.
Two partings closed with it. A bare row used as an operand answered E0401 in the tab against the compiler's E0409, and a write through a str slice was a tier refusal in the tab against the compiler's E0416; both are settled and both now answer what the compiler answers. Those were the two messages /install/ has been counting since the last bump, and the count is zero.
And a new parting opened, pointing the other way, which is why neither page simply got its old sentence back. Every parting this site has described ran one direction: the compiler had a clause, the interpreter predated the text, and the tab refused a program the compiler ran. This one is the reverse. The interpreter takes a one-line if — if c then a else b — that the compiler this page advertises refuses, because that spelling landed on the compiler's trunk after the release was tagged and the interpreter mirrored it early. It was measured through the module this build publishes rather than inferred from the tag: the bare form runs and prints what the upstream witness records, a let in a bare branch is E0201, and then still works as an ordinary name. Neither the clause nor its twelve witnesses are in the pinned corpus, so no verdict table could have shown this and the census did not move a single row.
That is worth stating as a rule and not an anecdote, because the site's own tripwire cannot see it. The counted gap is computed by ancestry between two specification revisions. It says which text each machine was built to read. It does not say the two read it the same way, and it is blind to a clause that is in neither the release nor its corpus. Both pages now say so beside the zero.
The packaging doors were read live, as they always are, and this time they did not agree — which is the first time this page has had to say that. The tap had moved past this site in the same commit that caught up with it: its lupin formula builds 0.1.33, the interpreter described here, while its wolf formula already builds a compiler release newer than the one described here, cut minutes earlier. On the AUR neither half had moved. One door ahead, one behind, and the page says both rather than waiting for a tidy hour.
The run census did not move: 308 phase: run programs, 211 exits, 31 traps, 66 unsupported, 0 mismatches, the sample menu at thirty-six and the seven declined tiers unchanged in number and in wording. That was predicted before the module was built, and for a structural reason worth keeping: only the interpreter moved at this bump, so the corpus the census walks was byte-identical to the last one and only verdicts could move. The two that moved are both phase: resolve.
ww26 (item 3) — 2026-09-11
The book's gitlink takes bs40, d001309 to d3536df. Chapter 5 is rewritten on the map alone: the opener counts words on a Map by itself, the list is kept only for the order a map does not promise, and a read of an absent key answers none rather than a sentinel. Seven places that wrote a negative one as a subtraction now write it as a negative one. All four counts on the book clock reddened and all four stood, which makes three bumps running — and no two of the three stood for the same reason.
The first of the three stood because the diff missed everything this site counts. The second stood because two things in the diff could have moved a number and, read closely, did not. This one is the largest book change a site bump has taken — three hundred lines in one chapter — and it lands on the one chapter whose title this page quotes. So the check is not "did the table of contents change" but "did the chapter survive the rewrite as the thing the Foundations line says it is": its heading is still Collections and generics, its five sections are all still there, moved down the file rather than renamed or merged, and the chapter does not join the held set. The colophon is in the diff again, and again its change is confined to the console block that prints the toolchain pair — the reservation sentence under it is byte-identical.
The counts were then re-derived from the checkout: thirty-three chapters in the table of contents, thirty-one written through, chapter 21's five sections with two held, and part 5's six programs and a coda.
One correction to what the audit file said at the last bump. It defended the written-through figure with the claim that chapter 29 is the only chapter carrying no sections and chapter 25 the only one carrying a single section. Chapter 29 is; chapter 25 is not alone. Chapter 13 carries its second section and nothing else, chapter 23 carries two of its four, and both have been that way at every pin this clock has audited. Neither is subtracted from the figure, because the rule this page states beside the number is that a chapter holding something back says so on its own page — which is why chapter 21 is counted written through as well. The figure is the chapter total less the two chapters the page names. The number was right and the test under it was not, so the test is the thing that changed.
ww25 (item 2, third bump) — 2026-09-11
The book's gitlink takes bs38, f1eb3bb to d001309: the book pins lupin 0.1.31, re-homes Scope and Proc, and fixes a Windows transcript that byte-compared on unix only. All four counts on the book clock reddened and all four stood again, which makes two bumps running — but the two stood for different reasons, and that is the part worth recording.
At the bump before this one the diff simply missed everything the site counts. This one did not. The colophon is in bs38's diff, and the colophon is where the book states that two chapters and five sections are reserved — the sentence this site's written-through figure is the outside view of. Reading the diff stat would have said "colophon changed, re-read the reservation"; reading the diff said the change is confined to the toolchain pin inside the wolf --version block, and the reservation sentence is byte-identical. Separately, /reading/'s concurrency line quotes the book's own titles for chapters 10 through 17, and a sprint that re-homes Scope and Proc is exactly the kind that renames "Scopes as values" — the table of contents is untouched, so it did not.
Two near misses and no edit. The counts were then re-derived from the checkout rather than from either the diff or the prediction: thirty-three chapters in the table of contents, thirty-one written through with chapter 29 still the only one carrying no sections and chapter 25 still the only one carrying a single section, chapter 21's five with two held, and part 5's six programs and a coda.
ww25 (item 2, second bump) — 2026-09-11
The book's gitlink moves again, 3edba7d to f1eb3bb, and this entry exists to record that nothing the site says about the book changed. bs39 rewrote chapter 5's second section, its exercise and the matching solutions; book/SUMMARY.md is not in the diff. So all four counts on the book clock reddened and all four stood: thirty-three chapters, thirty-one of them written through, chapter 21's five sections with two reserved, and part 5's six programs and a coda. Each was predicted from the diff before the audit ran and then re-derived from the checkout rather than from the prediction — the chapter total off the table of contents, the written-through figure off the two chapters that hold a whole page back, chapter 21's sections off its own headings, part 5 off its range.
A clock that reddens when nothing has moved is not a false alarm, and it is worth writing down once that this is the expected case rather than the interesting one. The bump before this was the other kind: chapter 33 and Appendix E arrived and two of the same four numbers moved. Neither bump can be told from the other without the re-read, which is the whole argument for the clock having no way to be satisfied except by someone doing it.
ww25 (item 1) — 2026-09-11
The site takes the interpreter's turn. lupin moves 0.1.31 to 0.1.32 and wolf stays v0.2.10, so the lupin clock alone turns: six literals, the two release-bound lupin-stamped sentences and the one live lupin count reddened, and every wolf and book entry held untouched.
The pin names no release, and the phrase does not move for it. lupin 0.1.32 was built to e0ce018, which is not a tag but a development revision of the compiler's trunk — twenty-nine commits past v0.2.9 and twenty-two commits short of v0.2.10, the release this site advertises. The interpreter took the revision that carries the clause it was mirroring rather than whichever revision wore a tag, which is the ordinary thing for a mirror to do and the first time this site has had to say it about a pin that sits *inside* the advertised release. It was predicted from the two gitlinks before anything was rewritten, and the ancestry gate agreed on every part: the newest release the revision carries whole is still v0.2.9, so the counted gap is still one and the phrase for one stays on both pages; the reserved sentence for a pin that is no release comes back, off both pages since ww23; and the stamped distance moves from fifty-one commits to twenty-two. Nothing about that was assumed to be zero — the temptation was real, because the revision is an ancestor of the tag, and a page that read the distance instead of the ancestry would have said so and been wrong.
The divergence table empties, and the parting count does not go with it. The census was predicted before the harness ran and held on every number and every witness: 308 phase: run programs, 211 exit, 31 trap, 66 unsupported, 0 fail, candidates 242. The compiler pin stands still, so the corpus is byte-identical to ww24's and every movement is the interpreter moving under it — exactly two verdicts, grammar/match_range.lu and grammar/match_range_char.lu, from fail(E0201) to exit(0). All four of the witnesses the range ruling brought in now agree with the compiler, the two that run and the two negatives: an empty range is E0815 on both sides and an open end is still the slice spelling and still E0201. wolf-interp#83 closes.
What does not go away is the parting, and that is the sentence this pin is worth. The table has no rows and two corpus programs still answer differently, because neither is a program either machine runs: a bare row used as an operand is E0409 on the compiler and E0401 here, and a write through a str slice is E0416 there and a tier refusal here. Both are wolf-interp#85 and both are open. /play/ has said twice that a table of verdicts cannot show every parting; this is the pin where believing the table alone would have reported a parting of none. The page says so in those words now, and /install/'s cost paragraph says the matching thing from the other side — what the distance costs is no program you can run, and two messages in programs nobody runs.
grammar/match_range.lu joins the menu, which makes thirty-six. It is the only menu entry ever held off for one pin and admitted at the next, and the rule that held it off is the one this site will not bend: an entry that answers fail looks broken whatever the page says beside it. It sat one ruling away from match_switch.lu, which was on the menu through that whole pin because its guarded arm is a lowering rather than a pattern form — the two on opposite sides of the menu were the whole of what the vintage cost, and they are on the same side now.
The packaging doors were closed before this lane read them. Read live at the bump: the tap's lupin formula builds tag v0.1.32 and the AUR carries lupin and lupin-bin at 0.1.32-1, while both compiler packages hold at 0.2.10-1. That is the second bump running where the doors moved with the release, and it is the whole reason those two claims are literals rather than stamps — had the doors not been closed, the sentence would have had to say so.
The other five lupin literals are history and were re-read against the module this build publishes rather than against memory: the trap-field story at 0.1.22 and 0.1.23 stands, the net trio has been in the interpreter since 0.1.28 and still declines on the socket alone, and 0.1.25's byte cast ladder is still closed — typecheck/byte_casts.lu exits 0 in the tab at this pin. /play/'s Seven tiers rides the lupin clock and was asked directly: all seven reason strings are in the census, unchanged in number and in wording.
ww25 — 2026-09-11
The site takes the rest. No release moves: wolf stays v0.2.10 and lupin stays 0.1.31, and the only clock that turns is the one this repository has never described in a whole sentence — the book's gitlink, which publishes no version and so is audited by its revision alone.
The book grows a chapter and an appendix. The pin moves to the book's trunk at 3edba7d, and with it chapter 33, the serving loop, and Appendix E, the driver's surface. The chapter reads last in part 4 with a number out of order, because section numbers are the book's permanent anchors and a late chapter takes the next free one rather than move everything after it; the reading page now says that, since a reader who sees a part labelled 18–25 and then 33 is owed the reason. The appendix is the whole command surface in one place, replayed from the driver's own help, and the back matter sentence names it — and names the index, which that sentence had quietly omitted since it was written.
All four of the book clock's counts reddened at once, and two of them were wrong. Predicted before the audit ran and held: the chapter total moves thirty-two to thirty-three, the chapters written through move thirty to thirty-one, and the other two stand — chapter 21 still carries five sections with two reserved, and part 5 is still six programs and a coda. Nothing else in the book moved a number the site states: chapter 29 is still reserved whole, chapter 25 is still one section in, and the colophon still counts two chapters and five sections against the whole book.
Does the book's gitlink want a stamp of its own, like the interpreter's? Half of one. The interpreter's stamp exists because two pages PRINT a revision, and that sha was rewritten by hand at every bump while the prose audit saw no version and the count audit saw no number. No page prints the book's revision — it reaches a reader only through the version stamp and the rendered changelog header, both written by the build from the gitlink — and the revision sweep that lane installed already refuses a git revision typed into any page, whatever it names. So there is nothing for a revision stamp to hold. The counts are the other half, and this pass is the argument: of the four, the chapter total is a pure measurement of the pinned checkout, with no judgement in it at all, and it is one of the two that moved. That one wants to be a stamped count off the book's table of contents, the way the corpus numbers already are. The remaining three stay literals on the book clock, because each states a reading the book does not publish about itself — which chapters are written THROUGH, and what counts as a program rather than a coda. Filed as wolf-web#28.
wolf-book#1 is closed from both ends (wolf-web#24). This repository shipped the book's css/ out of its theme and allowlisted one dead link on the duplicated first-chapter page, because the render emitted neither; the book's own render lane took both fixes, and walks and holds its own links now. Both workarounds are gone, and the cost of dropping them was measured at each pin rather than assumed: at the pin before, 46 dead links, of which 45 were pages asking for a print stylesheet that was not there; at this pin, none, across 1439 internal links and 63 pages. link-allowlist.txt is comment-only for the first time since it was written.
Every revision this site publishes is seven characters, asked for rather than inherited (wolf-web#26). Bare git rev-parse --short picks its width from the object count of the repository being asked, so the three pins in the version stamp came out at two widths, and every post-deploy check since ww20 has carried an undocumented slice on the compiler's to compare it with a page. Seven is now spelled out at each of the seven places build.sh asks git for a short revision, and at the deploy's own log line, matching what the gitlink-derived specification pin, the page stamps and the count audit's book clock already did by construction; and the build refuses to serve a version stamp whose revisions are any other width. The post-deploy checks drop their slices. It is wolf-lang#301's ruling for the development build stamp, one layer out.
This file is served prose, and the count audit reads it now (wolf-web#27, in part). render-changelog.py turns it into a page under /changelog/, and both audits took site/ as their root, so every number here was held by nothing — which is how ww24's own entry went out claiming three counts of its allowlists, two of them wrong, in a paragraph about the discipline of counting. The count audit takes it as a second root. A changelog entry is the record of what one wave measured, so every number in it is frozen by construction and carries no clock; the value is that writing a number into a served sentence costs an allowlist line, everywhere, which is the moment its author has to derive it. The version-literal half of that issue stays open and says why: this file carried thirty-four distinct version literals across a hundred and fifty-three mentions the day this entry was written, all of them history, and the version allowlist's grammar requires a clock on every entry — so admitting them would redden the whole file at every release, and the fix is the frozen-entry idea the count allowlist already has, spelled for a second file.
ww24 — 2026-09-11
The site takes the tenth. The pin moves to wolf v0.2.10 and lupin stays 0.1.31, which is the mirror image of the three waves before it. Every entry on the wolf clock reddened at once: eighteen in the version allowlist covering twenty-eight literal mentions, four in the stamp allowlist covering seven release-bound sentences, and six in the count allowlist covering nine counted occurrences. The nine lupin entries across the same three files held untouched, and so did the four on the book's clock. All of the wolf ones were re-read; all but the freshness pair stood.
The lag reopens at one release. lupin 0.1.31 was built to 4c60946, which is still the commit v0.2.9 was cut at — a tag, and now the tag before the one this site advertises. So the phrase for zero comes off both pages, the phrase for one goes back on, and the distance the build stamps returns: fifty-one commits of the compiler's own history between the two gitlinks. The ancestry count the windows job has done since ww20 decides all of it, and neither page writes a revision or a distance by hand.
The census was predicted before the harness ran and held on every number and every witness. 308 phase: run programs, 209 exit, 31 trap, 66 unsupported, 2 fail, candidates 240. This time the interpreter pin stands still, so the published module is byte-identical to ww23's and every movement is the corpus moving under it: twelve programs are new, none is gone, and nine more changed body without changing class — ch.send(v) became ch.send(v)?, because send is typed () ! {closed, cancelled} at this release.
Six of the twelve new programs are phase: run and each was placed from the release notes and the tier rules before anything ran. grammar/match_switch.lu and conc/chan_send_closed_row.lu exit 0; typecheck/unit_context_discard.lu and conc/spawn_tail_send_raised_row.lu decline on the task tier, which is the tab and not the pins; grammar/match_range.lu and grammar/match_range_char.lu answer fail(E0201), which is the pin lag with a face on it. The other six are negatives the compiler refuses, so the census does not walk them.
/play/'s divergence table is back, with two rows. It was empty at exactly one pin in this page's history, ww23's, and what refilled it is not the interpreter falling behind on something it had implemented but the compiler tagging a new pattern form: match gained range arms and lupin 0.1.31 was built before that text existed, so it stops at the .. inside a pattern and says so rather than answering wrongly. wolf-interp#83 is the mirror and the two rows come off together when the interpreter tags against this release.
The page also says what a table of verdicts cannot. Four more programs part at this pin and none is a row, because no machine runs any of them: two range negatives refused at a different site on each side, and two wolf-interp#85 witnesses that part in *which* refusal — E0409 against E0401 on a bare row used as an operand, E0416 against a tier refusal on a write through a str slice. A page that counted only verdicts would have reported a parting of two and stopped. The prediction caught the same shape one layer up: the sprint named four range witnesses as census movers and only two of them are phase: run.
grammar/match_switch.lu joins the menu, which makes thirty-five, and it is the first menu entry chosen to stand beside a table row rather than away from it. The two range programs are refused in the tab and the switch is not, and the difference is exactly what the vintage costs: the guarded arm the switch turns on is a lowering the compiler gained, not a pattern form, so this interpreter has always run it.
The front page's three release paragraphs were rewritten for r15's three rulings — match in statement position as the switch a reader expects, E0416 saying a str slice is no place instead of the compiler apologising for its own ledger, and W0601 making a fallible send in a loop body a warned discard rather than a mismatch. Of the seven release-bound stamped sentences the wolf clock put up for re-reading, three were rewritten (that paragraph, /spec/'s 01-grammar status line, and /play/'s vintage sentence) and four stood. /spec/ moved with the four documents the release touches. One new number word is listed for the first time: the seven characters of a development build's commit stamp, a ruling rather than a measurement, and counted on the wolf clock because a release is the only thing that could change it.
The package freshness literals moved WITH the release, which has not happened before on this page. Read live at the bump: the tap's wolf builds tag v0.2.10 at revision 662b14c and its lupin builds v0.1.31, and the AUR carries wolf-lang and wolf-lang-bin at 0.2.10-1 beside lupin and lupin-bin at 0.1.31-1. Had the doors not been closed, the sentence would have said so, which is the whole reason those two claims are literals and not stamps.
One stamp entry's count drops because a check changed hands. /install/'s pin-lag sentence no longer names the compiler's version at all — at a gap of one it names the tag before it — so the stamp allowlist audits one fewer bound sentence there, and the windows job's page-wide phrase check, which reads the two gitlinks rather than the prose, is what holds it instead.
ww23 — 2026-09-10
The site takes the tail. The pin moves to lupin 0.1.31 and wolf stays v0.2.9, so one clock moves again — six lupin literals, the two release-bound lupin-stamped sentences and the one live lupin count reddened, and the wolf entries were held untouched.
The lag is zero, and this time the pin is a tag. lupin 0.1.31 was built to 4c60946, which is not a revision of the compiler's trunk but the commit v0.2.9 was cut at and the commit this site pins. The counted gap has been zero once before, at ww19, and that pin was a release commit too; what is new is that the interpreter now names the release rather than a development revision of the trunk past it, so the reserved sentence about a pin that is no release comes off both pages, the phrase for zero comes back, and the distance the build stamped at the last two pins is gone because there is no distance to state. The ancestry count the windows job has done since ww20 prints it: same commit, zero commits back, and both pages say so.
The census was predicted before the harness ran and held on every number and every witness. 302 phase: run programs, 207 exit, 31 trap, 64 unsupported, 0 fail, candidates 238. The compiler pin does not move, so the corpus is byte-identical to ww21's and ww22's, and exactly one row moved. strings/concat_mix_char.lu went from unsupported to exit(0): + between a str and a char in either order, the last of the five witnesses v0.2.9's release notes named as still parting, and the row /play/'s divergence table has carried alone since ww21.
The three other things 0.1.31 does were checked against the corpus rather than read off the release notes and believed. Two of them are new refusals — a body whose tail slot is () under a declared return type, and a bare fallible row used as an operand — and either could in principle have turned a running program into a refused one, or got in front of a tier refusal and turned an unsupported into a fail. Neither can here: both are decided from declarations alone, wolf 0.2.9 refuses both shapes, and every phase: run corpus program is a compiler-accepted program. The third resolves a type annotation's name at last, which cannot bite a program whose names are all in scope. :type at the prompt has no surface in a page with no prompt.
/play/'s divergence table is retired, not shortened. It has listed rows at every pin this page has had — five at the interpreter's release before last, one at the last — and with concat_mix_char mirrored it has none. A table with no rows is not a thing to serve, so the page says the parting is none and says what it measured to know it.
That is a statement about these pins and not a promise, and the page says that too. The compiler's trunk has ruled range arms in a match since this interpreter tagged, and this build answers E0201 to the witnesses that carry them. None of those programs is in the corpus v0.2.9 ships, so none of them is in the census behind the page and none is something a reader can meet with the compiler /install/ hands them today; the mirror is filed as wolf-interp#83 and the table comes back the day this site advertises a compiler that carries the arms. The site cannot pull them forward on its own, either: the zero branch of the pin-lag gate requires the two gitlinks to be the same commit, so a compiler pin moved off v0.2.9 to reach the new witnesses reds the job by name.
The package freshness literals were read live and every channel had shipped. The tap's lupin formula builds tag v0.1.31 and the AUR's lupin and lupin-bin are at 0.1.31-1, while both compiler packages sit at 0.2.9-1 and the tap's wolf builds v0.2.9. That is the second bump running where the doors were current before the pin landed here, and it is still the reason those two claims are literals rather than stamps.
One allowlist line was deleted rather than re-read. /install/'s v0.2.8 was the tag the pin-lag paragraph named as the newest release the interpreter had read; at a gap of zero there is no such tag to name, the sentence is gone, and a counted literal whose sentence has been deleted is a number that can only drift.
ww22 — 2026-09-10
No pin moves. This wave closes the hole ww21 wrote down and leaves the site saying exactly what it said before, by measurement rather than by hand.
The specification revision the interpreter was built to — the seven characters /install/ and /play/ print three times between them — was the last fact in the pin-lag paragraph a person typed. It is no version, so the literal audit cannot see it; it is no count, so the count audit cannot either; and it was rewritten by hand at every interpreter bump since the paragraph was written. It is stamped now, off the interpreter's own gitlink — the same one the distance beside it is counted from — and the sha is checked against the compiler this site advertises before it is written, so a page cannot print a revision the pinned checkout does not carry.
The stamp on its own would have moved the hole one step, because nothing stopped the next editor from typing the sha back in beside it, which is what happened four bumps running. So the same pass refuses a git revision written into any page it sweeps, and it refuses the correct one too: a literal that is right today is precisely the kind that goes quietly wrong at the next bump. The test suite plants one and watches the build red. The windows job's pin-lag step, which already reads that gitlink to count the distance, now requires the stamp on both pages and reds on a literal there as well, and version.json records the revision the build measured, so what the site serves can be held against the build's own answer without asking git anything.
The wasm build script says what it does. Its header opened on a portability patch and called patching the staged interpreter the normal case; that patch landed upstream and was deleted on 2026-08-13, and every build since has reported that there is none. The staging is what the paragraph is about now. The header also states the toolchain requirement once (wolf-web#14): the staged version pin governs the build only when rustc is rustup's shim, so another rust ahead of it on PATH defeats the pin silently. The script warns where the pin is staged, and when the wasm target then turns out to be missing it names the rust that answered rather than sending the reader after a rustup problem that is not there.
wolf-web#5 is declined. The alternate multi-cursor chord would take Ctrl+Shift+arrows from every learner who expects it to extend a selection, to spare some Windows learners a driver hotkey they can turn off — and /play/'s note already points at the two paths no driver intercepts. The bindings stand as ww06 wrote them.
ww21 — 2026-09-10
The site takes the mirror. The pin moves to lupin 0.1.30 and wolf stays v0.2.9, so one clock moves: the six lupin version literals, the two release-bound lupin-stamped sentences and the two live lupin counts reddened, and the nineteen wolf literals held untouched. That is the per-clock audit column doing the job it was added for, and it is the first bump where it has had a quiet half to leave alone.
The census was predicted before the harness ran and held on every number and every witness. 302 phase: run programs, 206 exit, 31 trap, 65 unsupported, 0 fail, candidates 237. The compiler pin does not move, so the corpus is byte-identical to ww20's and every class move is a ruling the interpreter mirrored. The movers were not read off the release notes and taken on trust: every phase: run file was scanned for the constructs 0.1.30 gains — a leading else, pop on an empty list, get out of range, first, last, the Closed spelling — and each hit was placed in its class before the module was built. Four moved and the scan says why the near misses did not: the two overflow_list_pop witnesses pop a list that is not empty, and the ruling took the empty case only.
The fail class is empty again. It has been non-zero at exactly one pin in this site's history, ww20's, and the two grammar witnesses that put it there are the two the mirror has just taken. A tab no longer hands back E0005, which is the code the compiler retired in the release the interpreter was catching up to.
/play/'s divergence table goes from five rows to one, which is what a mirror catching up looks like from here. strings/concat_mix_char.lu is what is left: + between a str and a char, declined in the tab and open upstream as wolf-interp#78. The row that came off quietest is conc/chan_closed_row.lu, the one no verdict count could ever see — both machines exited 0 at every pin and only the printed bytes parted — and it prints closed now. strings/byte_view_lend.lu came off for a reason that was never a vintage at all: it was declined because this machine's List had no first, and the same ruling that fixed pop gave it one.
The lag is still one release and still not a release-shaped gap. lupin 0.1.30 was built to 2c03ed9, wolf trunk past the s144 merge, dev-stamped for the same reason 0.1.29's revision was: the clauses it mirrors do not exist at the newest tag. So the counted phrase does not move and neither does the reserved a development revision. What moves is the distance — fourteen commits behind the tag, where the last pin was twenty-four — and the distance is a stamp, so it re-rendered itself and the windows job's pin-lag step went green on the first run for the first bump in three. The step that has caught the SOURCE of this number twice running had nothing to say, because ww20 built it to count by ancestry and ancestry is what this pin needed.
The revision sha is the one fact in that paragraph no gate holds. It is not a version, so the literal allowlist cannot see it; it is not a count, so neither can the other. It appears three times across /install/ and /play/ and all three were changed by hand. A sha in prose is the next hole of the shape this site keeps finding.
The package freshness literals were read live and both channels had already shipped. The tap's lupin formula builds tag v0.1.30 and the AUR's lupin and lupin-bin are at 0.1.30-1, while both compiler packages sit at v0.2.9, which is current. That is the exact opposite of ww20's reading one wave earlier, and it is the whole argument for keeping those two claims literals rather than stamps: a stamp would have asserted the same thing on both days and been wrong on one of them.
ww20 — 2026-09-09
The site takes the line. Pins move to wolf v0.2.9 and lupin 0.1.29, both clocks in one bump for the first time since ww15, so all 25 version literals, all 11 release-bound stamped sentences and all 14 live counts reddened at once and every one was re-read.
The front page tells it the way the release does, as three things a reader ran into and none of them something wolf meant to teach. An aligned if chain whose else began its own line was an error, E0005, because the newline after the } ended the statement; a line whose first token is else continues the previous statement now, and E0005 leaves the catalogue. The formatter did not move, which is the half a reader needs: wolf fmt still lays a chain as } else { on one line, so the canonical shape is what it was and the aligned source formats to it. to_int's error row is spelled parse rather than NotAnInt — the one CapCase payload-free mark on the builtin surface, in a language whose own lint says otherwise — and it is the one change here that can break a program you already wrote, out loud: a match arm naming NotAnInt is refused with a note reading the cases are: parse. And a char joins a str, because a Unicode scalar appended to text is closed under UTF-8 and has one rendering, while str + int still refuses and the clause now says why. v0.2.8's three paragraphs came off the page; under this stamp they were false.
The census was predicted before the harness ran and held on every number and every witness, which mattered more than usual because this time it was not a cheap prediction. ww19's corpus was byte-identical to ww18's; this one moves — eight programs added, one deleted, two flipped from phase: resolve to phase: run because the compiler now accepts what it used to refuse. Each of the twelve was placed in its class from the release notes and the tier rules before the module was built. 302 phase: run programs, 202 exit, 32 trap, 66 unsupported, 2 fail, candidates 234.
The fail class stops being empty. It has been zero at every pin this site has ever measured. grammar/else_chain.lu and grammar/else_default_newline.lu exist to pin the layout v0.2.9 admits, lupin 0.1.29 has not mirrored it, and the tab answers fail(E0005) — a diagnostic code this same release retires from the compiler's own catalogue, so the playground hands back a code /docs/ no longer lists. Neither program is on the menu and neither should be.
The lag is one release, and it is not a release-shaped gap. lupin 0.1.29 was built to e9a17cb, a development revision of the compiler's trunk taken because the clauses it mirrors did not exist at the newest tag — twenty-four commits short of the commit v0.2.9 was tagged at and past the tag before it. So the interpreter has read the first of this release's three rulings and not the other two: it spells parse, and it has not mirrored the leading else, the closed/cancelled tags, pop on an empty list or str + char. /play/ tabulates the five programs where that shows with the verdict the tab gives for each, including the one no verdict count can see: conc/chan_closed_row.lu exits 0 on both machines and prints Closed where the compiler prints closed.
That distance is measured rather than written. It is a stamp now, filled in at build time by a new speccommits source that counts commits with git over the two pinned gitlinks — the first count in the site's scheme whose source is history rather than a file, which is why the build job now deepens the pinned compiler checkout that actions/checkout took at depth 1.
The windows job went red over the SOURCE of the number for the second bump running, and this time the changelog retires as a source altogether. It asked the pinned lupin CHANGELOG which release the interpreter's pin is, and 0.1.29 has no answer to give, because e9a17cb is not one and no sentence can make it one. The gap is the newest release tag that is an ancestor of that pin now, counted over the pinned compiler's own history. Two ancestry checks come free and are asserted: the pin must be a commit the pinned compiler carries, and the compiler pin must descend from it. Whether the pin is a tag is a second fact about the same gap and gets the same both-directions prose check the four phrases have — a development revision is reserved vocabulary on /install/ and /play/ now — and each page must carry the distance stamp exactly when there is a distance to state.
Two sentences had gone false carrying no version and no count word, which is the class neither audit can see and the reason the windows job holds prose at all. /play/ said two of the net calls were newer than this interpreter, which stopped being true at 0.1.28 and was still on the page a bump later. /docs/ said its code counts were not measured, which stopped being true at ww19, when they became stamps. Both say what they mean now.
The package freshness literals were read live, and the two channels disagreed, which is the ordinary state rather than a fault. The tap carries both tags this release describes; the AUR's two interpreter packages are at 0.1.29 and its two compiler packages are still building v0.2.8. That is the whole argument for keeping those two claims literals: a stamp would have asserted, with nobody looking, that a packager had already pushed.
wolf-web#19 closes. Every python script here runs twice — once on a CI runner during the PR, once on the deploy host when trunk lands — and those were not the same interpreter with nothing saying so. The host's version is written down in one place now and two gates read it: the build job installs exactly that python before anything else runs, so a PR run is the deploy's rehearsal and all seven scripts are exercised under it; and ci-deploy.sh prints the host's version beside the one CI rehearsed on and refuses to deploy when they differ. An upgraded host is a red deploy that names both versions rather than a silent change of what a green PR means.
ww19 — 2026-09-09
The site takes the interpreter. Pins move to lupin 0.1.28, and for the first time the counted lag is zero: 0.1.28 was released against 5c729e8, which is the commit v0.2.8 was tagged at and the commit this site already pinned. Both /install/ and /play/ say "the same commit", and neither carries a phrase for one, two or three releases.
The census was written down before the harness ran and held on every number. 293 phase: run corpus programs, 200 exit, 31 trap, 62 unsupported, 0 fail, candidates 231, identical to the pins before these. It was a cheap prediction to make: the compiler pin does not move at this bump, so the corpus is byte-identical and only the module in the tab changes. wolf-interp#69 is the one item in the release that could have moved a verdict, since it reparses str.to_int as the i64 the type is, and the corpus witness does not reach it. wolf-lang held the overflow input in its own crate tests while the two implementations disagreed, so strings/to_int.lu shows the i64 extremes and nothing outside them, and it exits 0 here as before.
Closing the gap changed nothing about what the playground declines, which is what ww18 said would happen and what both pages now say instead of leaving a reader to infer it. net_writev and net_nodelay are in this interpreter and fs_fstat was never a question of age, so all four witnesses still report unsupported in the tab. A browser has no sockets and this interpreter opens no files anywhere. Those refusals had two reasons at once while the pins were apart and one of them has gone.
Five literals reddened on the lupin clock rather than the four this pass expected. The four historical ones on /play/ hold, and byte_casts.lu still exits 0, so "0.1.25 closed that and it has stayed closed" is still a true sentence. The fifth is /install/'s package freshness claim, and it is a claim about somebody else's repository, so it was read live: the AUR has lupin and lupin-bin at 0.1.28-1 and the tap's formula builds tag v0.1.28. The doors were already current, which is the opposite of the ww18 reading and the reason that sentence is a literal instead of a stamp.
One sentence on /play/ had gone false while carrying no version at all. The to_int sample "could not have been on the menu at the last pin", which stopped being true the moment those became the last pins. It says what it means now.
The windows job stopped the build before any of it, over its own defect rather than the site's. It read the interpreter's specification pin out of a sentence ("Released against pin x, wolf-lang vY"), and 0.1.28 does not write that sentence. wolf-interp records the pin as a submodule gitlink, which cannot be worded differently, so that is where it is read now; the changelog is still consulted, but only to say which release that commit is, and it has to name that commit rather than some other pin in the same entry. A counted gap of zero also gets proved outright now: the phrase the pages carry says "the same commit", so the two gitlinks have to be it.
Every count in the prose is held by something (wolf-web#17). Eight numbers were written as English words with no gate anywhere, and one of them was wrong: /play/ said twelve of the corpus's net programs were declined here from ww16 to ww18, when thirteen was already the truth and it is sixteen now. Five of the eight come off something a build can measure and are stamped from it, the way file sizes have been since ww13 — the diagnostic and warning code counts off the pinned documents' own headings, the net count off the pinned corpus/net/, the menu count and the front page's "thirty-odd" off the sample index the build wrote. A placeholder names a source and a spelling, because the pages spell a number three ways and all three have to come off one measurement.
The other three are a reading rather than a measurement: the tiers the browser build declines, the things a Windows program can still reach, the rows the native lane skips. Those are literals in scripts/count-allowlist.txt, which lists every number word in the site's prose with a total and says how many of its occurrences a pin bump can still move. A live one carries an audited-at and reddens at the bump, so it is re-read against the thing it counts. There are three clocks now, because /reading/'s chapter counts move with the book, which publishes no version and so rides its pin. The frozen ones carry no clock at all, which the file requires rather than permits.
Counting a directory is only the right measurement while the whole directory behaves the way the sentence beside it claims, so check-samples.mjs takes every program in corpus/net/ through the module the build publishes and requires all sixteen to be declined. The count and the claim are held separately, because the count was wrong for two releases and the sentence around it was not.
ww18 — 2026-09-09
The site takes the first chapter. Pins move to wolf v0.2.8; lupin holds at 0.1.27 for the third release running, and this is the bump where that starts to cost something.
The front page tells it the way the release does, learner first. Chapter 1 of the book asks a string for a number twice, and until v0.2.8 the compiler refused that call, so the first non-trivial string method a learner met was also the first program that would not build. str.to_int() -> int ! {NotAnInt} is in the builtin set on both tiers now, spelled the way the reference interpreter has always spelled it, and text it cannot read is a row your program can branch on rather than a trap. At the other end of the language a parking call in the net family tries the syscall before it waits, so a read on a socket the program's own net_wait just reported ready touches no lock handshake, no condvar and no kevent. lobo went from 53.3 to 21.2 microseconds a request on one box in one session, and the page says the thing that matters about that pair: other lanes were working on the machine, the benchmark tool refused both sets under its own quiet-rig rule, and neither is an entry in lobo's ledger. What the two share is the box, the hour and the load, so the ratio is the claim and the score is not.
Six predictions, six holds. The class of every new corpus witness was written down before the harness ran, and the harness agreed: net/syscall_first.lu, net/writev_gather.lu, net/nodelay.lu and fs/fstat.lu answer unsupported, strings/to_int.lu exits 0, and rows/to_int_not_an_int.lu exits 1 carrying NotAnInt out of main, which is the exit class and not trap. The aggregate landed on the predicted numbers too: 293 phase: run programs, 200 exit, 31 trap, 62 unsupported, 0 fail, candidates 229 to 231.
Worth saying why the four refusals are over-determined, because the two reasons come apart at the next interpreter release. Three of them are tiers a tab cannot serve, and the filesystem tier is declined in every build of the interpreter including the terminal one. They are also calls this interpreter predates. When lupin catches up they stay unsupported on the tier alone.
The menu is thirty-four. strings/to_int.lu joins it, and the reason runs backwards from the usual one: the interpreter has answered str.to_int all along, so the program the tab could always have run is the one that only just started compiling. Every other release has moved the compiler ahead of the playground.
And the count that no gate held was wrong. /play/ has said twelve of the corpus's net programs answer unsupported since ww16, and thirteen was already the truth when it said it. Sixteen now, measured rather than incremented. The census exists to be re-measured and this is what re-measuring is for; a number written by hand beside numbers that are gated reads exactly as trustworthy as they do, and is not.
The lag is three, and it has teeth the last two did not. v0.2.6 and v0.2.7 moved no language surface, so an interpreter a release or two back still answered every program the compiler did. v0.2.8 adds four calls the pinned interpreter predates, so a program using net_writev, net_nodelay or fs_fstat compiles from the archive and is declined in the playground, and that is the pin lag rather than a disagreement about the language. Both pages say so now instead of leaving a reader to infer it.
The windows step stopped the job before any of that, which is what it is for. The gap counter ww17 built has prose for a closed set of gaps, it counted three, it found no sentence for three on either page, and it named what was missing rather than guessing. The phrase went into the table after the paragraphs were written. One wrinkle came out of it and is recorded beside the table: those phrases are reserved vocabulary on /install/ and /play/, because the absent-phrase half of the check is page-wide, and the packaging paragraph below wanted to say "one release" about something else entirely.
/install/ names what the doors carry, twice in one sprint. ww17 wrote the package freshness claim as counted literals rather than stamps, on the grounds that a stamped claim would re-render with the new number at the next bump and assert, with nobody looking, that a packager had already pushed. Both literals reddened at this pin. The live AUR and the live tap were read and every compiler package was still at v0.2.7, so the page said so. Two hours later the packaging landed, the channels were read again, and the page says v0.2.8. A stamp would have been accidentally right the second time and quietly wrong the first, which is the whole argument for the literal: it is a claim about somebody else's repository, and the only way to know is to look. What stays on the page is the process fact rather than the state, since a release and its packaging are separate acts and do not land in the same minute.
One thing this pass nearly got wrong. The v0.2.8 release was still a DRAFT when the pin was taken, with two of its four archives uploaded, so for a few minutes the front page's "four archives" and /install/'s Windows download link named files that did not exist. The release finished publishing before anything was committed and all four are there. The site's rule is that a page cannot claim a version of wolf that does not exist, and a tag is not the same event as a release.
ww17 — 2026-09-09
The site takes the pairing. Pins move to wolf v0.2.7; lupin holds at 0.1.27, which is the whole shape of this bump. The compiler tagged and the interpreter had no reason to.
What v0.2.7 is. wolf --version prints a second line naming the lupin release the compiler is differentially tested against, and that line has one job, which is to be true. It stopped being true when the interpreter published 0.1.27 and the compiler's stamp still said 0.1.26. The gate that exists to catch that did catch it, on every machine in the house at once and on none of the six CI jobs, because the comparison needs a lupin binary standing beside the compiler and no runner had one. The release re-measures the pairing against the interpreter as released and gives the linux job the pinned lupin release archive, fetched by the digest the release page reports and checked against the bytes that arrive. No language surface moved. The front page carries the story and nothing below it moves for it.
The census did not move, which was the prediction. 287 phase: run corpus programs, 198 exit, 31 trap, 58 unsupported, 0 fail, candidates 229, every number identical to ww16's. The prediction was cheap to make and worth making anyway: no corpus file was added or removed between the two tags, the only corpus edit is a conforms: tag and a comment on corpus/test/conc_schedules_test.lu (a program the playground already declines), and the module in the tab is unchanged because lupin did not tag. The numbers were re-measured through the published module rather than reasoned about, because the rule this file keeps is that they are read, not glanced at.
Thirteen literals reddened, every one of them on the wolf clock, and the four lupin entries stayed green because 0.1.27 is still 0.1.27. This is the first bump where the two clocks disagreed, which is what ww15 built them for. Nine release-bound placeholder sentences reddened beside them (ww16), and four of the nine were false.
The four. On the front page, "At (the wolf stamp) a loser comes back inside the budget its net_deadline armed" was v0.2.6's fair accept rendering under v0.2.7; it names v0.2.6 now. On /install/, "the fair accept (the wolf stamp) is named for is here too" was the same sentence one page over. On /spec/, document 11's "at (the wolf stamp) it states what those hands actually see ([os.net.accept])" was the third copy of it. Document 05's was different and more interesting: "at (the wolf stamp) the clause caught up to the practice" was true of v0.2.6, where four namespaces the documents were already publishing in got appended to [conf.anchor.ns], and false at v0.2.7, where the register moved again in the opposite direction. 07-schedule-points.md had been declaring seven [sched.*] anchors that no register carried, so no extractor read the document and no gate held an opinion about it, while the native runtime cited four of the seven from live scheduler code. The page says both halves now, and says the clause is checked in both directions.
The lag is two, and the step that measures it was wrong about why. The windows job has held the pin lag since ww11 and accepted zero or one since ww14, with a comment saying a lag of two means a lupin release was skipped or a pin was never bumped. Neither happened. The compiler tagged twice (v0.2.6, v0.2.7) while the interpreter held, because a pairing re-stamp gives an interpreter nothing to catch up to. So the ceiling is retired. The step counts the gap as the distance from the advertised release back to the one lupin names, over the pinned CHANGELOG's own ordering, and a gap the pages have no words for stops the job instead of being called a skipped release.
And the number is now held against the prose, which is the half neither audit can see. The lag is stated three times across /install/ and /play/, and the third of the three, "one release behind the compiler's at these pins", carries no version literal and no placeholder at all. Both audits were green over it while it was stale, through this bump and, going by the sentence itself, the one before. The step requires the phrase for the counted gap on both pages and the phrases for the other gaps on neither, so a paragraph left unrewritten stops the job and says which page it is on.
/install/ says how the doors open. Two channels carry wolf and the project publishes both, and until now the page mentioned neither. Homebrew wants brew trust wolffe-lang/wolf before brew tap wolffe-lang/wolf && brew install wolf, and the page says so because skipping the trust line produces an error that is not true: Homebrew declines to load formulae from an untrusted third-party tap and reports the refusal as invalid syntax in tap!, which is its generic wording for a tap it would not read. Both formulae parse. The tap carries wolf, lupin and lobo. On the AUR the packages are named for the language, because wolf there is Return to Castle Wolfenstein and has been for years: wolf-lang and wolf-lang-bin, lupin and lupin-bin, lobo-bin. The freshness claim (all eight packages across the two channels named their upstream's latest, v0.2.7 for the compiler and 0.1.27 for the interpreter) is written as counted literals rather than stamps, one on each clock. A stamped freshness claim would re-render with the new number at the next bump and assert, with nobody looking, that a packager had already pushed; as literals they red and someone reads the live channel.
Filed upstream, wolf-lang#264. v0.2.7's own CHANGELOG entry documents #253 and nothing else, but the tag also carries the s139 admission above (#246) and the s140 driver work: a real wolf --help on stdout at exit 0 with per-verb help, a man page and shell completions, a compile-failure footer that names the code it actually reported, and a use std.… miss that says no standard library is configured. Four of those issues are still open. The site renders that CHANGELOG from the pinned checkout, so /changelog/wolf/ now publishes a v0.2.7 entry with two thirds of the release missing, and nothing on this side can fix it.
ww16 — 2026-09-07
The site takes the accept. Pins move to wolf v0.2.6 and lupin 0.1.27, and the front page tells v0.2.6's story instead of listing it.
The class the tripwire could not catch now has a rule. The ww15 pass found two sentences falsified *by the version stamp itself*: a placeholder is exempt from the literal allowlist by construction, so a page that renders "byte arrived at this release" becomes false the moment the stamp moves, with nothing anywhere to notice. scripts/stamp-allowlist.txt generalizes the two hand fixes. It enumerates every placeholder occurrence with a total and how many of them are release-bound (true only of the release the stamp names), and a file with a bound one carries an audited-at the pin bump moves past as it does for a literal. The clock is the placeholder's own name, which caught a hole in the older half: the pin-lag paragraph reads "lupin *(the lupin stamp)* was released against v0.2.5", whose only literal rides the *wolf* clock, so a lupin-only release would have falsified the sentence without ever re-reading it.
Six sentences were wrong at this bump, and only two carried a version anywhere. /install/ "because *(the wolf stamp)* is a release about writing servers" and /spec/ "at *(the wolf stamp)* it gained what a serving loop needs" were both true of v0.2.5 and both rendered under v0.2.6. /install/ "the call *the release* is named for" reads the stamp with no version in it at all. /install/ "it was zero a release ago" and /play/ "the gap was zero one release ago" were true when the ww15 pass wrote them and false a release later, because the lag has been one for two releases now. /play/ "that was not true two releases ago" counted lupin releases from the stamp and drifts every bump. All six are literals the allowlist counts. All twelve pre-existing literals went red at the bump, both clocks at once, and every one was re-read at the tag.
The lag is one and the rule computed it. lupin 0.1.27 reads pin 6ade878 (wolf v0.2.5) while the site advertises v0.2.6, so ww14's "the two commits ARE the same commit" is false again and both pages say one release apart. The Windows job's 0-or-1 step printed it.
The accept, explained for a stranger. A server in wolf is several hands on one listening socket; every arrival wakes more than one and exactly one takes it. Through v0.2.5 a loser then parked in a blocking accept(2) with its deadline already spent, until the *next* connection: microseconds on a busy server and on a quiet one never. Now it comes back inside the budget its net_deadline armed, and nothing in net_accept's signature moved. Both numbers on the page are measured, and the page says what they were measured on. The compiler holds at ~30,000 req/s before and after (three hands, ab -n 6000 -c 32, macOS arm64; under load the fix buys nothing), and lobo deleted its ten-millisecond accept turn, re-run as an A/B in one session on macOS 15 arm64 with 18 cpus: 12,866 → 23,663 req/s at three hands and 9,554 → 38,961 at eighteen on keepalive. [conf.anchor.ns] and #239 get a line on /spec/ and nothing on the front page.
The census was re-measured and the prediction half held. 287 phase: run corpus programs, 198 exit, 31 trap, 58 unsupported, 0 fail, candidates 228 → 229. net/accept_race.lu is unsupported as predicted, but the aggregate was not: os/cpus.lu came *off* the unsupported rung, because lupin 0.1.27 registers os_cpus and the call no longer fails to resolve. It still cannot be answered in a tab. What comes back is the io row, which [os.cpus] requires of a host that cannot answer, in place of a silent 1, so the program exits 0 printing answered false. Verdict exit, and the menu stays at thirty-three, because a sample shown beside a header it contradicts looks broken.
And /play/ undercounted what it declines: "five tiers" omitted os_random and os_signal_listen, both of which report unsupported here and serve at a terminal. The page says seven now, with the two named (wolf-web#13).
ww15 — 2026-09-06
The tripwire sees lupin. scripts/check-version-prose.py has audited the site's version prose since ww07 (every literal allowlisted, counted, and re-read when the pin moves past its audit), with a lupin-shaped hole in both halves, filed as wolf-web#8 at ww11 and covered by hand at ww11 and again at ww14. That is two waves of manual work, so this wave fixed the rule first and proved it before taking a pin.
The audit names its clock. The checker reads two pins now, wolf's CHANGELOG heading and lupin's Cargo.toml version (the same two build.sh already derives), and an allowlist entry says which one audits it: audited-at-wolf= or audited-at-lupin=. The proof is the shape that cost the hand coverage. With the lupin pin moved and the wolf pin held, on ww14's tree with its checker and its allowlist verbatim, the old rule exits 0. This one exits 1:
version prose: play/index.html: '0.1.22' was audited at lupin 0.1.25, the lupin pin is now 0.1.26 — re-read the sentence, then re-stamp its audited-at-lupin
Both sentences were re-read against 0.1.26 and both hold.
And the regex sees a bare 0.1.x. It matched only v-prefixed literals, so lupin 0.1.22 (the spelling the site uses everywhere else, since the stamped placeholder renders without a v) was invisible: a claim about an old lupin could be written into a page and fossilize with nothing to catch it. The ww11 pass worked around that by spelling v0.1.22. That workaround is retired; /play/ spells both literals bare now, and with them deleted from the allowlist the old checker still reports a clean run while this one names them. The widened rule found something on its first pass, too: Rust 1.97.1 on the front page, a toolchain claim the site has carried unaudited. It has a line now, on the wolf clock, and the v0.2.5 bump forced its first re-reading.
The pins move to wolf v0.2.5 and lupin 0.1.26. Six allowlisted literals went red at the wolf bump and every one was re-read at the tag. Two more sentences would have been falsified *by the stamp*, the class no tripwire catches, because a placeholder is exempt by construction: /install/ and /spec/ both said byte arrived at this release and the unix-domain clause was new at it. Both arrived at v0.2.4, and both say so as counted literals now. The pin lag is one again: lupin 0.1.26 reads 982f857, wolf v0.2.4, while the site advertises v0.2.5, so ww14's "the two commits ARE the same commit" is false and both pages say one release apart. The Windows step stayed as it was; the ww14 pass made it 0-or-1 and it computes which.
Five named refusals on Windows, not three. wolf v0.2.5 is THE SERVER HAS CORES (s137), and this is the host where a prefork server is written a different way. reuse_port answers the unsupported row, and the refusal is the designed behavior: Windows has no SO_REUSEPORT, and SO_REUSEADDR is a false synonym that lets any process take a held port. Measured on a runner, two sockets carrying it bound one port and all sixteen dials went to the first bound, none to the second, so a worker that thought it had joined a group would sit idle forever. A descriptor handed across a spawn is missing twice over: the SOCKET does not cross the spawn the runtime performs, and a SOCKET is not the small stable number [os.proc.inherit] hands over by position. The page says what serves in the same breath, because a section listing only refusals would describe a worse host than the one a learner has: net_wait is the one s137 clause that names no refusal anywhere, os_cpus answers, and net_listen_with without the option is net_listen with a backlog hint. The Windows job holds all of it: four s137 corpus witnesses run on the runner for their pinned stdout with the exit-3 guard that catches a refusal arriving as the *wrong* row, two probes saying which branch this host actually took, and a third holding the positive half.
The census re-measured, and the corpus moved for the first time in two releases. Through the module this build publishes: 286 phase: run corpus programs (282 a release ago; s137 added net/wait_readiness.lu, net/reuse_port.lu, net/inherit_listener.lu and os/cpus.lu), 197 exit, 31 trap, 58 unsupported, 0 fail. All four new programs answer unsupported, so the candidate count is unchanged at 228: three want sockets a tab cannot open and the fourth wants the machine's core count, which is is18's os tier declining in the browser the way os/random already does. The is37 byte domain moved no menu entry's class: thirty-three entries, each in the class the page claims for it, none carrying a note, the cast ladder still exit(0). /changelog renders both new entries, THE SERVER HAS CORES and THE BYTE HAS A DOMAIN (is37).
ww14 — 2026-09-03
The ladder lights. The interpreter pin moves to lupin 0.1.25; the compiler pin does not move, and this entry follows from that. lupin 0.1.25 was released against pin 982f857 (wolf v0.2.4, the tag itself, the release this site advertises), so for the first time the two implementations behind this site are reading one revision of the specification.
The dark sample lights up, and the gate turned the page. The ww13 pass put corpus/typecheck/byte_casts.lu on the playground menu knowing it did not run, marked with a note explaining the refusal, and built scripts/check-samples.mjs to hold that note in both directions: an unnoted entry must answer exit or trap, a noted one must not. At the new pin the gate went red before anything else did:
FAIL corpus/typecheck/byte_casts.lu exit(0) (noted) runs at this pin, and still carries the note that says it does not — retire the note in scripts/collect-samples.py
Exit 1, one of thirty-three entries wrong. That red is the retirement mechanism working: the note came off because CI refused the build. Press Run on *the byte, and its cast ladder* now and it prints the line its own corpus header claims:
widen 200 200 | trunc 0 255 0 255 44 | arith 201 400 -1 -200 | order true true true | eq true true
exit 0, no diagnostics, no warnings, and the D72 ruling in one line: the widen is zero-extension (200 back, never -56), 256 truncates to 0 and -1 to 255 and 300 to 44 with no trap and no W0401, arithmetic widens to int first so 200 + 200 is 400, and the comparisons are octet order. All thirty-three menu entries run or trap by design now, and no sample carries a note for the first time since the notes were introduced at ww13.
The fail class is empty. Re-measured through the module this build publishes: 282 phase: run corpus programs (the corpus did not move), 197 exit, 31 trap, 54 unsupported, 0 fail, against 192 / 31 / 49 / 10 a release ago. Of the ten the interpreter rejected at 0.1.24, five run or trap now (the cast ladder among them, and grammar/bom_at_start.lu, whose leading ef bb bf this release strips the way wolfc does), and five report unsupported naming the tier that declines them instead of the type they could not resolve: fs_write_bytes and fs_create_dir_all do not exist in this machine, the s39 net tier has no sockets to open in a tab, and one wants a List.first the std subset does not carry.
The pin lag rule is 0-or-1 now, and the step says which. The Windows job has held the gap at one release since ww12, so that lupin catching up would go red instead of quietly falsifying /install/ and /play/. It went red. The step now accepts zero or one, computes which, and writes the sentence from the measurement: at these pins lupin reads this release — the page's sentence holds, and the run summary's table says A gap of 0. Two is still a red, because two means a lupin release was skipped or a pin was never bumped. Both pages are rewritten to match: /install/ says the two commits in wolf --version's second line ARE the same commit at these pins and that one is the usual gap, /play/ says a disagreement here is now about the text instead of a lag behind it.
Nineteen claims re-recorded, across /play/, /install/, scripts/collect-samples.py and the Windows job: every sentence phrased "one release behind", the whole "One sample this build refuses" section, the sample-selection census, and the job's error string, summary line and closing sentence. The lupin.exe link on /install/ and the Windows job's lupin smoke both follow the pin without a hand edit: the link is a stamped lupin-version placeholder and the job reads the pinned Cargo.toml, so both point at the 0.1.25 asset (5.3 MB, one file, about 5 MB still true) with no version literal to rot. (Writing that placeholder's name literally in this entry is the trap ww12's finished-dist sweep exists to catch, and it is dodged here the way it was dodged at ww13.) /changelog renders the new entry, THE BYTE ARRIVES (is36).
The two lupin literals /play/ carries by design (v0.1.22 and v0.1.23, about when the observation record started carrying a trapping program's output) were re-read by hand at this bump and both hold. They are audited against the *wolf* pin, which did not move, so the allowlist did not force that reading; wolf-web#8 is still the hole it was, and this is the second wave in a row it had to be covered manually.
One upstream finding rides along without touching this site: byte has the type but not the domain (wolf-interp#62). byte resolves and the casts hold, but 0..=255 is not enforced where an un-cast int flows into a byte slot: List[byte].push(256) stores 256 here where the compilers refuse E0401. All thirty-three menu programs were checked against it and none is exposed: the ladder truncates by clause (256 as byte), and the only other sample that touches the type is projects/rpn.lu, which reads bytes out of tok.bytes() with b as int and pushes nothing back in. is37 fixes it.
ww13 — 2026-09-03
The byte on the page. The pins move to wolf v0.2.4 and lupin 0.1.24, and the release's headline is a breaking change one line wide, so it is on /install/ rather than only in the changelog. str.bytes() yields byte now, an 8-bit unsigned octet, and the first thing anyone does with one is compare it to a number, which is E0401. The page quotes the compiler finishing that sentence: "byte adopts no literal and takes no int implicitly ([type.byte]): widen the byte — b as int — or narrow this side with as byte". The Windows job builds that program on a Windows runner, reads the note back, and then builds the line the note names and runs it: w is 119, exit 0. Exit 1 for the refusal, measured on the runner, and the page draws the distinction a learner needs: an ordinary program that does not compile, where this host's own refusals answer exit 2.
Unix-domain sockets are the third named refusal on Windows, new at this release. net_listen_unix compiles here and answers the unsupported row with a name instead of a bare io failure, which is the difference between a program that can branch on the host and one that cannot; the limits section's opener owns its count and now says three. Both halves are measured: the corpus's own witness runs on the runner for its pinned stdout and for the exit 3 it would take if a bind ever failed with a path row, and a second probe beside it says which branch of that construction this host took, because a witness that passes vacuously proves nothing about a sentence.
The playground runs a byte sample that does not run. corpus/typecheck/byte_casts.lu (the cast ladder, where 256 truncates to 0 and -1 to 255 and the widen is zero-extension) is the thirty-third program on the menu, and lupin 0.1.24 answers fail(E0301) on it at resolve, because as byte names no type that release knows. It is on the menu anyway, marked, with the reason on the page: the interpreter tagged its byte work at is35 and the type's producers landed after the wolf release 0.1.24 was built against. The page promises nothing about when that changes. A gate holds it rather than a memory: scripts/check-samples.mjs feeds every menu program to the wasm module the build published and refuses in both directions, so an unnoted entry must answer exit or trap and a noted one must not. The day the interpreter starts running it, CI goes red and the note comes off because it has to. The whole run set was re-measured through that module at the new pin: 282 phase: run corpus programs, 192 exit, 31 trap, 49 unsupported, and 10 fail, a class that was empty a week ago and one holding the nine byte programs plus grammar/bom_at_start.lu.
The pin lag is still one release, and the CI step stayed as it was. lupin 0.1.24 was released against pin 3befc3e (wolf v0.2.3) while this site advertises v0.2.4, so the gap the step holds at one is one, and the sentence /install/ and /play/ both write is unchanged. It would have gone to zero at a lupin 0.1.25; there is no 0.1.25.
Three sentences had rotted the way ww12's Windows headline did, each phrased relative to "the release before this one" and each false the moment the pin moved: native compilation arriving "one release ago", lupin's second-opinion distinction being "new at" this version, and the missing arm archive. All three name the release they mean now, as five new version literals, listed and audited in the allowlist.
And the measured sizes stamp themselves. The spec and docs pages print how big each document is so a reader knows what a link costs, and those numbers were written by hand: four had drifted (spec/01 52 to 53 KiB, spec/02 47 to 48, spec/10 13 to 17 because byte is declared in it, spec/11 14 to 16 because the first socket clause is), plus the diagnostics catalogue at 151 to 154. The ww12 pass had re-recorded three of the same class by hand a week earlier. They work the way version claims have worked since ww07 now: a page names the document it is sizing in a placeholder and the build fills the number in from the pinned checkout, refusing when the placeholder names a file the pin does not carry. (Writing that token literally in this entry is what the finished dist sweep added at ww12 exists to catch, and it caught it.) Fifteen numbers can no longer be wrong. The code counts are still by hand and were re-read at this pin: 136 diagnostics, 33 warnings, both unmoved.
Twenty claims re-recorded in all. /changelog renders v0.2.4, learner-first paragraph at the top: THE BYTE SHIPS. One finding went upstream the same day: lupin resolves a cast target by scope lookup, so as byte and a misspelled as itn produce byte-identical reports, and the note tells a reader to hunt for a typo that is not there (wolf-interp#60). The lupin-shaped hole in this repo's own tripwire (wolf-web#8) had to be dodged again, by spelling a historical lupin version v0.1.23 where a bare 0.1.23 would have been invisible to the checker.
ww12 — 2026-09-02
The flip. The ww11 pass left one line to change and a runner that had been recording the "before" every week: three corpus programs (the task layer, channel transfer, signal reception) run on a Windows box against the published archive and tabulated, asserted nothing, and waited for a tag. v0.2.3 is the tag. Against wolf-0.2.3-x86_64-pc-windows-msvc.tar.gz, fetched by the URL the page prints, all three serve: corpus/conc/spawn_fanout_loop.lu exits 0 printing 204, corpus/conc/message_passing.lu exits 0 printing nothing, corpus/os/signal_loopback.lu exits 0 printing reload, each byte-for-byte the stdout= its own corpus header pins. The job asserts them now.
So /install/'s limits paragraph retires. The twenty-one by-name refusals are gone, the windows-native serves no spawn/scopes transcript comes off the page, and two named refusals take their place: wolf build --release, exit 2, quoted as the runner printed it (the page had been truncating the sentence's parenthetical), and external reload/upgrade delivery, which has no Windows analog. The second needed care: the page must not say os.signal is unserved when the loopback witness prints reload on that host; what a learner cannot do is send one from another process. The no-toolchain refusal was re-captured at the tag and has not moved a byte. Three claims in the job inverted with the prose: wolf build fanout.lu now asserts exit 0, an ordinary fanout.exe, and that no refusal names windows-native at all.
The linux-aarch64 hedge retires too. The ww10 pass wrote "an archive per host that passes their own unpack-and-run smoke" because v0.2.2 shipped three archives and threw the arm one away (wolf-lang#213, filed at ww10, fixed here). Four archives at this tag, and a measurement replaces the qualifier: the Windows job HEADs all four release URLs on every push and requires 200 and a toolchain-sized body, 68.2 MB linux x86-64, 57.2 MB linux aarch64, 13.4 MB macOS, 11.5 MB windows. /install/ gained the paragraph that says what the arm archive serves, which is the checked tier, because that host still has no native backend.
The two pins are one release apart, and the site says so. lupin 0.1.23 was released against pin 8cda3aa (wolf v0.2.2) while the compiler this site advertises is v0.2.3. wolf --version prints both on the runner: wolf 0.2.3 (wolfgang, pin 3befc3e), then paired with lupin 0.1.23 (reference interpreter), pin 8cda3aa. /install/ records it beside the line that explains that output, the playground records it where the interpreter actually runs (the explanation for a program answering differently in the tab than under a freshly installed wolf), and a CI step holds the gap at one release in both directions: when lupin catches up, the sentence goes red instead of rotting.
The prose sweep at the new pin moved five more claims: the Windows headline said native build and run were new in the version build.sh stamps into it, which was true of the release before and would have printed a falsehood the moment the pin moved; the windows archive is 11 MB, not 10; three spec documents grew (01 50→52 KiB, 11 13→14, grammar.ebnf 9.6→10); the front page's pointer to /install/ now carries the release's headline; and lupin stops being described as the way to run what the Windows backend cannot reach, because there is nothing on that host it reaches that the compiler does not. The four allowlisted version literals were re-read at 0.2.3 and all four still hold. /changelog renders v0.2.3, learner-first paragraph at the top: THE ARCHIVE RETURNS.
ww11 — 2026-09-02
The site tells the parity. The playground moves to lupin 0.1.23, and all three of that release's visible changes were verified through the rebuilt wasm before a word was written. A program that traps no longer runs the defers it had pending: faults/trap_skips_root_defers.lu printed inner inner-defer before-trap root-defer in the tab at the previous pin and prints inner inner-defer before-trap now, which is what all three of the compiler's lanes print, so the witness joins the sample menu as *a trap runs no defers*, and a reader can watch the two implementations agree. The reason nobody could see that disagreement is the second change: through v0.1.22 the observation record reported stdout_inline: null on every trapping program, so the two machines were record-identical whatever they printed, and the playground's record note now says so beside the button that shows one. The third is a kindness a learner meets first: a missing comma used to answer `expected }, found identifier y and stop, and now finishes the sentence, "the members of a struct literal are separated — add the comma", with a second line, the comma goes here at 18:25`, pointing at the zero-width spot where it belongs. The primary span did not move a byte. The whole run set was re-measured against the new module: 270 conformance programs, every one in the verdict class it answered in at 0.1.22.
The compiler pin does not move. wolf's trunk already serves tasks, channels and signals on Windows, which will make /install/'s limits paragraph false the moment it is tagged, but there is no tag, no archive, and nothing this site's CI could fetch to check a sentence about it, so the page keeps v0.2.2's measured truth and promises nothing about the next release. What the Windows job gained instead is the measurement, taken early: three parity probes (the task layer, channel transfer, signal reception) run on the runner every week and are recorded, not asserted. At v0.2.2 all three refuse with exit 1, each naming the symbol that would not link (__wolf_rt_scope_new, __wolf_rt_chan_new, __wolf_rt_os_signal_listen) and the sprint that closes it. When the tag arrives, one line turns those three rows into claims that can go red.
The prose sweep at the new pin found two claims the menu had outgrown: the front page and the spec page both said the playground loads "twenty-odd" corpus programs, and it loads thirty-two.
/changelog re-renders with lupin 0.1.23. One finding went upstream the same day, against this repo: the version-prose tripwire cannot see a bare 0.1.22-shaped literal at all, and audits lupin sentences against the wolf pin because that is the only clock it reads (wolf-web#8).
ww10 — 2026-09-02
The learners' page. The pins move to wolf v0.2.2 and lupin 0.1.22, and the sentence /install/ was built to say last week is retracted: the compiler builds and runs native Windows programs now. wolf run hello.lu prints hello, wolf on a Windows box, wolf build leaves an ordinary hello.exe you can send to someone who has no wolf at all, and the page leads with that instead of with a refusal. One thing has to be installed beside it, Visual Studio Build Tools with "Desktop development with C++", and instead of describing what a learner without it sees, the CI job takes the Windows SDK away from its own runner for the length of one command and quotes the transcript: exit 2, and a refusal that names the three import libraries and the workload that carries them. The same job asserts the other half of that story, which is that no Developer Command Prompt is needed: wolf finds the toolchain itself from a plain shell with LIB unset. What still refuses is one region of the language rather than a tier: concurrency and the operating system's edges, twenty-one corpus programs. The page quotes that refusal too, with the compiler's own per-host ledger linked at the tag. wolf conform-run --checked, which was the only way to run a program on Windows through v0.2.1, is demoted to the specialist's tool it always was, and the paragraph teaching learners to type .\hello.lu is gone because wolf-lang#206 closed at this tag. The reference interpreter has binaries for the first time, so the section that pointed at nothing now links lupin.exe, one file, downloaded and smoked on the runner beside the compiler. The playground runs 0.1.22, whose headline is a region with a budget, and both cap witnesses are in its menu: the boundary that holds and the breach that traps with the ledger arithmetic in the message. Three findings went upstream the same morning: v0.2.2 shipped no linux-aarch64 archive because the new dist smoke gates upload on a native tier that host does not serve (wolf-lang#213), its release page carries no notes at all (wolf-lang#214), and lupin's comma refusals carry no "add the comma" note where the compiler's do (wolf-interp#56).
ww09a — 2026-09-02 — the editor gets its width back
- The playground's two-column split moved from 60rem to 96rem and became 3:2 in the editor's favor: below that, one column, with the editor at the page's full width and output beneath. At the old breakpoint the editor got ~30rem (about 50 columns),
wrap=offclipped the rest, and the empty output pane looked like reserved editor space (the human's report). CSS only; CSP, editor code, and markup untouched.
ww09 — 2026-09-01
The Windows welcome. The site gains /install/, and its Windows section was written from the measurement: a windows-latest job in this repo's CI downloads the published archive by the same URL the page prints, unpacks it with the same tar, and asserts every claim the page makes (word for word, exit code for exit code) on every push and once a week besides. What that job found is what the page now says. The compiler runs on Windows up to code generation: wolf --version, wolf --explain, wolf fmt, wolf test, and wolf conform-run <file> --checked, which executes a first program on the compiler's checked machine and prints its output in the record. wolf build and wolf run name the host in their refusal and exit 2, and the page quotes that refusal whole instead of paraphrasing it; native Windows builds are in progress and the page names no date. Two Windows-shaped facts a learner would otherwise hit blind are on the page because the runner hit them: a file saved by Notepad is not canonically formatted until wolf fmt rewrites it, and conform-run needs .\hello.lu where every other verb takes a bare name (wolf-lang#206). The reference interpreter is published as a binary for no platform at all, so the page says so and points at the playground rather than linking a Linux tarball (wolf-interp#54). The playground's own Windows caveat is now printed beside its key bindings (Ctrl+Alt+arrows is screen rotation on some laptops), and the editor's suites run on a Windows runner, where node reports Win32 and every binding is exercised in its Ctrl spelling.
ww08 — 2026-09-01
The site catches the wave. The pins move to wolf v0.2.1, lupin 0.1.20 and the book at bs22, four book sprints and two releases in one turn of the crank. The playground now runs an interpreter whose match arms take a struct apart by field name, and whose defer in a loop body fires at the end of every turn rather than when the function returns, which is the transcript the book's §4.3 now teaches. The book on the site gains the 45-exercise K&R ladder, chapter 25's first printed section on wolf publish, the corrected defer teaching and three pattern exercises: 327 exercises in the corpus, 278 printed on the pages, each with a solution. /changelog carries the book's own entries for the first time (bs19 through bs22) beside wolf 0.2.1 and lupin 0.1.20. Three prose claims the new pins moved were re-recorded, and the spec page's grammar/1 posture is now stamped from the pin at build time instead of naming a version by hand.
ww07 — 2026-08-31
The prose catches up. Every version claim on the site was re-recorded against the pins it already served: eleven stale claims, from "this is version 0.1.0" under a v0.2.0 toolchain to a spec page missing four normative documents. A tripwire now keeps the class at zero: current-version claims are stamped into the pages at build time from the pinned checkouts, and every literal version mention must be counted and re-audited on each pin bump or the build refuses. The site also grew /changelog: each public project's CHANGELOG.md rendered from the pinned checkout it was built from, with a repo that has none yet getting a page that says so instead of a broken link.
ww06 — 2026-08-30
The site catches up. Pins move to wolf v0.2.0, lupin v0.1.18 and the post-bs19 book, so the playground runs six releases' worth of interpreter (+10.7% wasm) and diagnostics point at line:col instead of byte offsets. The editor grows manners: auto-indent, wolf-aware bracket pairing (interpolation braces pair inside strings), goal columns, and multi-cursor editing with one-step undo. A CSP gate proves no script source was added to get them.
ww05 — 2026-08-26
The PDF on the page. The reading page offers the typst-set print edition for download, rendered from the same pinned book source as the web pages, with its measured size printed beside the link, and the line removed entirely when a build has no PDF, so the page never advertises a file the dist does not carry. Trunk work soon after fixed the page the fix shipped on: a 0600 file had been 403ing live, and the build now refuses any dist file nginx cannot read.
ww00-era — 2026-08-13 to 2026-08-22
The site exists. A landing page, the playground (lupin compiled to WebAssembly, running conformance-corpus samples entirely in the tab), the spec and diagnostic catalogues copied from a pinned compiler checkout, and the book rendered from its own pinned repo. Everything served is generated from submodule pins, so no page can claim a version of wolf that does not exist. CI builds on every push, a link checker walks the dist, deploys go to lupp.us over a restricted key, and a build missing a piece refuses to ship unless a waiver names it.