Phoenix

Phoenix  /  Postmortem

Postmortem

Written against the predictions the roadmap and journal made before there was evidence. A prediction that held is worth as little as one that failed unless both are written down; this file is where the scoring happens. COMPLETED.md is what exists; this is what was believed about it.

Revised after awk, which is the first language described here that was not chosen to suit the tool.

Phoenix is 10,193 hand-written lines of C11 with no dependencies (~14,600 if the generated runtime.h is counted; COMPLETED.md says why both are true). The descriptions written against it come to ~4,300 lines: Pascal in 1,434 (56 node types, a checker and two backends), Solveig in 1,094 (15 node types) with a bytecode backend, awk in 990 with a 682-line C runtime it embeds, calc in 494 across three backends, and Phoenix’s own notation in 281.


1. What was predicted, and what happened

Held: a compiler is a grammar plus a sequence of walks

The whole premise. Four real passes exist — a renderer, a symbol collector, a type checker, and several emitters — and none of them contains traversal code. The Pascal checker follows records, with blocks and forward declarations through four hops of an environment that is an ordinary list of pairs.

That worked for a reason worth naming: every hop points backwards, at a node the single post-order walk has already finished with. Nothing needed a second walk, a worklist, or a fixed point.

Held, and then failed, and then held: one implementation of the notation

A generated compiler runs the same lex.c, parse.c, eval.c and run.c that phx runs, over frozen tables. There is nothing for the two to disagree about, because there is only one of them.

That was said here, and it was not true. emit.c froze every string with strlen, so a literal holding a NUL arrived short while the length beside it still said otherwise — and the generated compiler read past the end of a string phx never had. Any description with a NUL in a literal, which is every description that emits a binary format, and languages/solveig/solveig-sob.phx had one from the day it was written.

Nothing noticed for two stages, and the reason is the more useful half: the test only ever compared text. The one backend emitting bytes had never been written out as a compiler at all.

“There is only one implementation” is a claim about the code. That it holds is a claim about the tests, and it is only as strong as the widest thing they compare.

The suite compares .sob files now, and a description whose literals hold a NUL in a grammar, a pattern and a template at once.

Held: tables-as-conditionals, in place of an if

Rescored on 2026-10-10, in § 39: failed at scale, and built.

Roadmap 3.5 called this “the assumption in the design most likely to be wrong” and asked to have its first failure recognised as a decision arriving. It has not arrived. lookup(t, k, default) carried an entire bytecode backend — three-way choices between a local slot, an outer slot and a global; an append-if-absent on four threaded tables at once — and never once wanted an if.

awk found the shape of it, and the shape is not an if. lookup is a function, so both answers are worked out before it chooses — fine until one of them cannot be, which an omitted for part is. The fix was in the grammar: an omitted part builds a node that renders as nothing, so every part is emitted the same way and the question stops being asked. Third time the answer to “the notation cannot say this” has been “say something else, earlier”.

Failed: the flat target would make %rewrite urgent

The design memo predicted that a flat target (assembly, bytecode) would be “what pure attribute grammars are worst at” and would make a second mechanism — rewriting with a traversal strategy — urgent rather than eventual.

It did not. The .sob backend is attribute clauses and nothing else. A length-prefixed table is size of what the children emitted, so it is synthesised. A name is an index into a table that grows as the walk goes, so the table is threaded.

%rewrite was built two stages later, and not for a flat target. What wanted it was an optimisation: solas compiles the block of an ifTrue: into the enclosing chunk, and written as clauses that meant conditioning thirty of Block’s on whether its parent inlined it. Taking the node out of the tree costs nothing instead. So the prediction was right that a rewrite would be wanted and wrong about every reason — which is the most common shape of a wrong prediction on this page.

Failed: the missing thing would be expressiveness in expressions

What actually gave way was a combination of two mechanisms that had no meaning. A threaded attribute accumulates left to right along the walk; an inherited one is scoped to a subtree. A .sob method chunk wants both — its own name and constant tables, accumulated in walk order, started empty on the way in and put back on the way out.

The fix was eleven lines: a down clause naming a threaded attribute sets the thread for the subtree. The save is then an ordinary down attribute, the restore is the node’s own leaving clause, and it nests because a stack nests. run.c still knows nothing about chunks.

The lesson is about where to look, not about what to add. Six stages of attention went to the expression language, and the gap was in the walk.


2. What the tests prove, and what they do not

Three kinds of check exist here, and they are not interchangeable. Ranked by what they can catch:

  catches misses
round trip — parse, render, parse, compare trees a node built with the wrong shape, a dropped argument, a chain folded the wrong way anything wrong consistently, because the renderer inherits the parser’s mistake
reading the output anything wrong on its face anything that looks right and is not
an oracle — compile it, run it, compare what it prints a wrong answer, whatever produced it a right answer reached wrongly (see below)

The round trip’s blind spot was demonstrated, not theorised. group parsed ("ran":display. { nil }) as ("ran":display. "ran":display) — the first statement twice, every later one dropped. The parse was wrong, the tree was wrong, and what show wrote back was wrong in the same way, so it re-parsed to an identical tree and the round trip passed. 75 files, every one of them green, for as long as the bug existed.

And an oracle has a blind spot too, also demonstrated: array [5..9] had one subtracted instead of five, so it wrote outside itself — and fpc agreed with it, because the write and the read used the same wrong offset. The answers matched and the memory did not. An oracle proves the answers agree, not that the program is correct.

The conclusion is that neither is optional and neither is sufficient, which is why both exist for every language: fpc -Miso for Pascal, solas + solvm for Solveig, /usr/bin/awk for awk.

awk demonstrated the round trip’s blind spot a second time, in a different language and within an hour of the description being written. Fourteen programs round-tripped to identical trees while two constructs were being written back out as programs awk rejects — if (c) { a }; else { b } and do { a }; while (c), where a ; after a block ends the statement and orphans what follows. Both read back as the same tree. It took running the rendering under awk to see it.

And a fourth kind, added since

  catches misses
comparing phx with a compiler phx wrote the two implementations drifting apart anything both do wrongly — and anything in a kind of output the comparison does not cover

That last clause is not hypothetical. The comparison existed for stages and compared only text, and a bug that could only appear in binary output sat in emit.c the whole time. A test’s blind spot is the shape of what it compares, not the shape of what it tests.

What the numbers are


3. What the design cost

A library that grew. 23 entries now. The rule each had to meet is written down — a pass for a real language needed it, and it could not be written in the notation — and each addition names what was missing. But roadmap 3.4 also says a generator whose library keeps growing has failed at something, and the last four arrived in two stages, three of them for one reason. That is the number to watch, and what it is measuring is below.

Repetition in the descriptions, and the thing behind it. The append-if-absent idiom appears six times in solveig-sob.phx, once per node type that interns a name, because the notation has no way to name a shared computation. The .sob line table then asked for something narrower and sharper: a value computed for every element of a list. each applies a template, and a template can only write an element out.

sizes and bytes-over-a-list were two cases of that answered one at a time, and both turned out to be cases of something else. The question was never a map over a list: it was an attribute every node has, of which a list of nodes then has a column. otherwise is that, it is still a clause about a node, and it took Pascal’s twenty-one type = "void" clauses down to one.

The two library entries stay, and they are the honest cost of answering a question one case at a time before seeing the shape of it.

No forward references — and it turned out not to be a cost. An attribute cannot refer to a node the walk has not reached, and several passes with a %driver are the answer. awk was described partly because it would test this: a function may be called above where it is defined. Checking those calls is two passes and twenty lines, because a leaving clause on the root runs after the whole subtree, so the forward reference is answered by the shape of the walk. Reference attributes are settled against — see COMPLETED.md.

Seven hundred lines of C in a string literal. The awk backend needs a runtime, because awk’s value model is a string and a number at once and C’s is not. Held as literals it could not be compiled — the artefact that had been tested against awk was not the artefact in the repository. %embed fixed it, and the rule it changes is worth keeping: a mechanism has to answer not only who else wants this but what can no longer be checked without it.


4. What was open here, and how each closed

Every question this section held has been answered, which is why it is written in the past tense. ROADMAP.md holds what is open now.

A reader-level mechanism, for @include. %include names which node an include is built as and which field holds the file, and the reader splices before the first pass; a backend has no clause for one and never meets one. It took the shape this section proposed. What it did not find is anything wrong with the backend over the 22 programs it added — the more useful half.

Debug information in a binary target, and it cost more than this paragraph expected. $pos answers Position(line, column, file, endline, endcolumn). “Everything needed is present” was true of reading a position and false of using one, and it took three more entries to finish: an attribute every node has, a span rather than a point, and inlining, before languages/solveig/ agreed with solas on every byte of every program.

Whether a description can share a computation. Answered, and the question was mis-stated. It was never a %pass-level definition or a rule other rules call — either would have been the first thing in the notation that is not a clause about a node. It is otherwise, which is the most general clause about a node, and the evidence that settled it was already in languages/pascal/pascal.phx before the question was asked.


What replaced them

The questions that arrived while these were being answered are a different kind, and worth naming because the shape repeats:

Every time this project has predicted a new mechanism, the answer has been the mechanisms already there, used in an order nobody had tried.

Four for four. The question a new entry now has to answer first is what does the walk already know, and when does it know it?


5. What has not been retracted

The plan was staged so that a wrong direction could be backed out to the last stage that was right. Nothing has been backed out. Every stage still stands, and the two designs most likely to fail — actions as a notation Phoenix owns rather than host-language splices, and one walk rather than demand-driven evaluation — are the two that made the later stages cheap.

awk is the strongest evidence for both, because it is the first language here that was not chosen to suit the tool. It has pattern-action rules and no main, no declarations, concatenation with no operator, a value that is a string and a number at once, and a grammar that is not LL(1). It reads, round-trips, type-checks its calls and compiles — and the six programs in its corpus were written by people who had never heard of this.

The ratio is the claim, and it is now measurable on somebody else’s code: 800 lines of awk in the corpus, 1,650 lines of description that compiles it, and the description works on awk nobody has written yet.


6. What the documentation week scored

Four kinds of page were made executable in one week — three tutorials and one reference section — and the results split cleanly enough to be a rule.

The prediction that failed: that reading a page verifies it

Every tutorial here was checked by its author while it was being written. That turned out to be worth nothing, and the reason is not carelessness:

An author runs the commands against files that already exist, in a directory that already has state. Neither is what a reader has.

Eight defects across three tutorials, and the shape of them is the tell. Three were a command run on an artefact nothing had rebuilt after the source changed — which does not fail, it silently answers about the previous build. One was the closing comparison of a page, the step the whole tutorial builds to, comparing two things that were no longer the same program. One was a command naming a driver the page never told the reader to write. One was a line number captured from a file that had a later section appended. One was a caret line two spaces short.

Not one would have been found by reading. All were found in a single pass by running.

The prediction that held: that a reference is different

docs/reference.md § 11 had eighteen of the library’s twenty-two functions unchecked. 66 claims were written from it, in the order the page makes them. All 66 held.

That is the more useful half of the week, because it says which pages need this:

A tutorial’s claims are about a sequence of commands in a directory, and go stale when anything around them moves. A reference’s claims are about the tool, and the tool has tests.

So the rule is not run your documentation. It is run the documentation whose claims are not already held by something else — and a tutorial is always in that category, because its subject is a session rather than a program.

What the checker did not catch, and now does

A field with a threaded attribute’s name was silent. A field is read before an attribute, so the clause meant to update the thread read the field instead: the thread never passed through that node, and every node after it carried on from a value that never went through. The chunk it produced was three bytes wrong with no diagnostic, and it took a byte-level diff against a golden to find.

The synthesised case had been warned about since the check was written. The threaded case is the sharper one — the synthesised attribute is merely invisible from outside, while the threaded one takes a value out of the fold and puts a different one back — and it is an error now rather than a warning, because there is no reading of it that is correct.

Settling the remaining case of that family turned up a documentation bug of exactly the kind § 6 is about: docs/manual.md stated the name-resolution order backwards. A binding wins over a field; the page said the reverse.

And two entries left the roadmap with evidence rather than opinion

2.3 had been open on scepticism for three languages. Describing Turbo Pascal’s units settled it against: resolution stayed an association list, and a cycle between two implementations costs nothing because visibility does not compose — there is no traversal for a cycle to be a cycle in. The entry’s leading criterion turned out not to be sufficient, which is a better result than closing it would have been, because it names what to look for instead.

1.6 was the last unfinished work in any language described here. Its prediction was half wrong in the useful direction: printargs needed no telling where to stop, because awk itself keeps | as the redirect inside a print.

Section 2 of the roadmap is now empty and section 1 holds one entry, which is a measurement rather than work.

A small one, scored the same way

The day’s closeout predicted that § 5’s known warts were likely stale, reasoning that labels, slots and threads had all changed during the week. Checking found one stale entry and it was a count — the other four were exactly as true as when they were written.

The real defect in that section was omission: three warts had accumulated without being written down, and the newest of them — that nothing in the notation iterates over data — had a worked example and a divergent/ file before it had an entry.

It is a small thing, but it is the same shape as the tutorials: the guess about what had rotted was wrong in both directions at once, and only reading the actual text settled it. A page is not stale because the code around it moved; it is stale where a specific sentence stopped being true.

7. The one estimate the standup made

Closing 2026-09-03 the standup put symbolic slot operands at the top of the outstanding list and predicted the size: the frame already knows the names, so it is small.

It held, and the reason is worth separating from the result. It was not small because the change was shallow — every slot operand moved from a field to a child node, across two files and four instructions. It was small because three mechanisms were already there and each was there for a reason that had been written down:

The estimate was right because the parts had been named. That is a different claim from the change was easy, and it is the one worth carrying: a mechanism that was given a reason when it went in can be reused without re-deciding anything.

What the estimate did not include, and should have

Two refusals and a diagnosis. A frame with two slots of one name had to be refused; outer reaching past the outermost frame had to be checked rather than merely reported badly, because the first message written for it — no slot is called x here — was true and was not the mistake.

That last one is not a rounding error on an estimate. It found a gap that predates the feature: outer 5, 1, numeric, at the top level, had always assembled cleanly. Writing a message forced the question the feature had not raised, which is the third time this week a page or a message has found a defect the code did not.

8. Three predictions in one afternoon, and what each was worth

The afternoon made three calls in a row and got a different kind of answer to each. Together they say something the individual entries do not.

Right, and for a reason that generalises

Deferring the outer slot bound was called a decision rather than a typing job, on the grounds that it widens slot n is past this frame, which has m from local to outer and that message has a test naming it. That held. What it did not anticipate is why it was a decision: the bound was in the wrong file. It came from a slots in the source, so it was a program check sitting in the pass that makes bytes, and moving it to solvm.phx covered outer as a side effect rather than as work.

The estimate was right about the shape and wrong about the cause. It was called a decision because of a test; it was a decision because of a boundary.

Wrong in a direction worth having

The change made another check unreachable — layout’s one-byte bound on a slot, which a frame of at most 255 slots strictly dominates. That was not predicted at all, and it is the more interesting half: the byte bound had never been a check, only an ordering. It fired first because it ran first.

Nothing about writing the feature would have surfaced that. It surfaced because a test failed with a better message than the one it expected, and the failing test was read rather than repaired.

A test that fails with a better answer than it asked for is reporting a design change, not a regression.

Not a prediction at all, which is the point

The third was refusing to answer anything open? from memory. Twice that question had already found a defect the same day, so the third asking got a method: count every attribute’s definitions against its reads, and walk verify_chunk rule by rule instead of consulting the README’s summary of it.

It found nothing wrong — and that is the first time this week the answer has been nothing, said with something behind it. The README now carries the verifier as a table so the claim is re-checkable in a minute.

What the three have in common

Two of the day’s three defects came from reading a bound rather than running one: d < 1 in a line of C that was summarised instead of quoted, and a nesting limit that looked identical on both sides. The third came from writing a number from memory that grep -c would have given exactly.

The repository already knew this about documentation — run the documentation whose claims are not already held by something else, § 6. Today extended it to bounds, which are documentation of a sort:

A bound is a claim. Generate the program that sits on it.

9. “Anything open?”, asked four times

The question was asked four times on 2026-09-03 and found something every time: a partial-write bug, a missing role, a false claim about outer 0, and two errors in a page published an hour earlier. That is no longer luck, and it is worth saying what makes it work and where it fails.

It works because it changes the method, not the effort

The first three askings were answered by checking rather than recalling: counting definitions against reads, walking verify_chunk rule by rule, generating a program that sits on the nesting bound, running git log -S for every feature a page dates. None of that is more careful reading. Re-reading is what let each defect through in the first place.

A second reading of a page you wrote finds what you already believe. A command that disagrees with it finds what is true.

Where the answer aged badly, and why that is not a contradiction

The third asking was recorded here as this time nothing was open, and it was true when written. Within the hour a changelog was published with two errors in it. Nothing was retracted: “nothing is open” is only ever a claim about the work that exists when it is said, and the answer had been correct about a repository that did not yet contain that page.

The useful form of the rule is therefore narrower than it looked:

Asking anything open? audits what has settled. It cannot audit what was written since the asking — and the most likely thing to be wrong is the newest thing.

One hedge, scored

Finding COMPLETED.md’s stage table wrong, the decision was to flag it rather than fix it, on the reasoning that it might be describing the plan rather than the delivery — its lead says Seven were planned.

The hedge was reasonable and it was wrong. There was a plan table, and it was in the README, where nothing had looked. COMPLETED.md’s was the delivery and simply incorrect. What settled it was not more thought about the wording; it was git ls-tree stage-3, which shows examples/calc-c.phx already present and therefore shows the plan’s stage 4 arriving inside stage 3.

The generalisation is the one this section is about, arriving from the other direction: the hedge was an attempt to resolve a factual question by reading more carefully. It was a factual question. It took one command.

10. The measurement that was predicted to change nothing

ROADMAP 1.2 was described three times as a measurement that has come out the same way, and the standup twice recommended against a fourth on those grounds. The prediction about the result was right and the recommendation was wrong, which is a distinction worth keeping.

Right about the number

The curve was flat again, in three new shapes, exactly as predicted. Nothing about compiling the tables to code became more attractive.

Wrong about the value

Two things came out of it that a fourth flat line does not describe.

The measurement gained a control. The three previous grammars were a shallow ladder, a deep one, and a deeper one; all three are expression languages. languages/solvm/ has no expression grammar at all, and measuring it turned the curve is flat in every grammar tried into something with a shape: the constant tracks ordered-choice depth, the curve tracks nothing, and the case where generating code would help least costs within 2× of the case where it would help most. That is an argument. Three flat lines were an observation.

And the instrument was broken. bench/run.sh could not report a failed run, so two published numbers had been parsed out of an error message. That defect was two days old, sat in a file whose entire purpose is to produce trustworthy numbers, and would not have been found by any amount of reading — only by running it on an input that fails.

What the recommendation should have been

Do not expect a different answer was correct. Therefore do not measure did not follow, and the reason it did not is the same one this document keeps arriving at from other directions:

Re-running a check that has always passed is how you find out the check stopped working. A result that never changes is indistinguishable from a measurement that stopped measuring — until you look at the instrument.

11. What closing the last entry was worth

The roadmap reached empty on 2026-09-03. Two things about that are worth recording, because empty is easy to mistake for finished.

The measurement did not decide it; the control did

1.2 had been measured three times and left open all three, correctly: three flat curves are an observation, not an argument. Every grammar measured was an expression language, so flat in all of them had nothing to be flat against.

The fourth measurement supplied that, and the entry closed the same week. The number did not change. What changed was that a number finally had something to be compared to.

Three measurements of the same kind are one measurement repeated. The fourth was worth more than the first three because it was a different kind.

And emptiness is a claim about the present tense

The page now says nothing is open. That is not a claim that nothing else will be built — it is a claim that nothing is currently owed, and the difference matters because every entry that ever arrived on that page arrived the same way: a real language needed something the notation could not say.

The risk of an empty roadmap is that it stops being read, and then the next thing that ought to go on it goes into a commit message instead. The mitigation is the one this repository already uses everywhere: phoenix/library.c is a separate file so that every addition is visible as an addition, and § 3.4 says a growing library means the notation was not expressive enough and nobody noticed. An empty roadmap fails the same way, and the same tell catches it.


12. “About fifteen lines”, and what a second backend actually cost

ROADMAP.md § 3.1 predicted the size of the thing that would restore the conformance rule over loops: “A second emit backend is what would restore it, which since %import is about fifteen lines.” calc-awk.phx is that backend, so the estimate can be scored.

Wrong by half, and wrong for a reason already written down

Twenty-four clause lines, plus the %pass header, three %driver lines and one %import — twenty-nine against a predicted fifteen, so the estimate was out by about half.

Where the missing half went is the part worth recording, because the number it left out is exact. Nine of the twenty-four clause lines answer for nodes calc’s own grammar never writes: Binary, Compare, Logical, Negate and Not all arrive with lib/expression.phx, and several of them need more than one clause because their operators are spelled differently in every target anybody has. The other fifteen are calc’s own.

Fifteen. The estimate was very close to right about the language calc declares and left out the module it imports entirely.

calc.phx states this cost in as many words — “expression.phx builds these three, so calc has to answer for them even though it did not write the grammar that makes them. That is what importing a grammar module costs” — a comment sitting directly above the clauses that were forgotten. A cost written down in a comment is not a cost that has been counted.

Right about the shape, which mattered more

The half of the prediction that held is the half %import was argued for. calc-awk.phx contains an %import line, an emit pass and three drivers, and nothing else — no grammar, no typechecker, no interpreter, nothing that can drift from calc.phx because there is no copy of it.

The C backend is also twenty-four clause lines, and seventeen of them are identical to awk’s character for character. The seven that differ carry five differences, and every one is the target forcing a difference rather than a choice: awk’s special variables are ordinary assignable names, so a variable gets a prefix; its / is floating division, so truncation is modelled; it has no declarations, so let and := are one shape; print x > y redirects to a file, so the argument list is parenthesised; and its unit of execution is a rule, so a program is a BEGIN block. A diff between the two files is a list of the ways awk is not C, and nothing else.

Fourteen lines of underestimate against a file that cannot go stale is the trade 1.3 and 2.4 both made, and it is worth recording that the number was the wrong thing to have predicted.

What it bought, which the estimate never mentioned

The prediction was about size and the entry was about coverage, and the entry was the interesting half. Before this, programs/fizz.calc — the only calc program with a loop and a branch in it — was checked by one backend against "1 2 300 4 5 300 7 8 300 10 11 300 13 14 300 ", a string typed into tests/run.sh by hand. It was right. Nothing in the suite could have told anyone if it had not been.

That expectation is gone. Both backends compile fizz.calc, both run, and the two outputs are compared with each other. To confirm the check can fail, the awk backend was mutated twice and the comparison caught both:

mutation what awk then printed
int({} / {}) → ({} / {}) — drop the truncation model 300 fifteen times. Floating division makes n / 3 * 3 = n true for every n, so the branch always takes the same arm
while → if — a loop that runs once 1

The first is the one worth the entry. It is a plausible output — fifteen lines of a number the program really does print — produced by a backend that is consistently wrong about the one thing semantics.md exists to pin down. No interpreter leg could ever have caught it, because the interpreter refuses that program. That is the failure the conformance rule was written against, and until today calc’s looping programs were not covered by it.


13. The empty roadmap, and the risk it named coming true

§ 11 was written on 2026-09-03, two days before this, and it named a risk:

The risk of an empty roadmap is that it stops being read, and then the next thing that ought to go on it goes into a commit message instead.

That happened, and it took two days. The roadmap is not empty any more: 1.7 and 2.5 are open. Neither is new work discovered by having a good idea. Both are failures that were already written down somewhere else:

entry where it had been living instead
1.7 — a repetition that counts CHANGELOG.md 2026-09-03, in the sentence announcing the assembler: Phoenix can emit .sob and cannot read it
2.5 — circular attributes languages/units/divergent/three-cycle.pas, a test file, since the day units were described. Refused since 2026-10-10, and in tests/

Not a commit message, which is what § 11 guessed. A changelog entry and a test fixture — both durable, both version-controlled, both entirely invisible to anybody asking what is left to do. The prediction was right about the mechanism and wrong about the hiding place, and the hiding place is the more useful half: those are better places to lose something than a commit message, because they look like records.

What actually surfaced them, which was not diligence

A survey of comparable tools. Not because it found problems — it found none this project did not have written down — but because it supplied the names: Kaitai Struct’s repeat-expr for the first, JastAdd’s circular attributes for the second.

That is what moved them. Section 2’s own header says each entry is somebody else’s solved problem, and a cost with no prior art attached does not read as one. Both had failed the page’s admission test for days on a technicality: they were things the notation could not say, and nobody had a word for what would fix them.

An open question with no name stays a complaint. Finding out what other people call it is what makes it an entry.

And the mitigation § 11 offered did not fire

§ 11 proposed a tell: a growing library.c means the notation was not expressive enough and nobody noticed, and an empty roadmap fails the same way, so the same tell catches it. It did not. library.c gained nothing in those two days, because neither of these wants a library function — one wants a grammar operator and the other an evaluation strategy.

The tell was calibrated for the failure mode the project had already met. It does not catch a limit that shows up as a workaround outside the tool: 1.7’s symptom was reaching for solvm --dump, another project’s binary, and no file in this repository grew by a line when that happened.


14. The counts check, scored the same day it was built

COMPLETED.md makes a claim about tests/counts.sh in the section that introduces it: it would have caught all nine. That was written on the morning of 2026-09-05 and had two pieces of evidence against it by the evening, one on each side.

Wrong, within the hour, and the reason generalises

There was a tenth stale number, and the check did not have it. lineage.md opened with 5,300 lines of C11 — exact when written on 2026-09-01, and 63% understated four days later. The check covered COMPLETED.md and postmortem.md and not that file.

Not an oversight of a known case: the check was written from the list the sweep had produced. Its inputs were the nine numbers already found, so its coverage was those nine numbers’ homes. The question it should have been written from is which records quote numbers, which is a different question with a different answer.

A check written from the cases you found is a check against the cases you found. Assemble it from the question instead — what kind of thing can be wrong — or it inherits the blind spot of whatever found the first batch.

And a second reason it survived, which is about numbers rather than about checks: 5,300 is close to what the descriptions now come to, 5,395. A stale number that looks wrong gets fixed on sight. One that looks like a different true number does not, and this one had been read past for four days.

Right, four hours later, in under a minute

Fixing the syntax-error position added fourteen lines to parse.c. That made three records stale at once — COMPLETED.md twice, lineage.md, and postmortem.md, which the check still did not cover and does now — and make test refused the build before the commit existed.

That is the claim holding in the form that matters. The nine had been wrong for days because nothing looked; this was wrong for the length of one edit, and the thing that noticed was not attention.

What the pair is actually worth

The check did not make the class impossible, and it was written as though it had. What it did is change how long a wrong number survives — from days to the gap between two commands — for the records it covers, which is a smaller claim and a true one.

The two failures also have opposite shapes and both are worth keeping: coverage decided by where the last bug was found, and a number wrong in a way that reads as plausible. Neither is fixed by care. The first is fixed by deriving the check’s inputs from the question, and the second is not fixed at all — it is the reason the check has to exist. —

15. Four predictions about one afternoon, and the one that was cheap to be wrong about

Every one of these was made and settled on 2026-09-05, most of them within a few hours. They are worth keeping together because three failed in the same direction: the plan named the thing it had last thought about, and the work wanted the thing next to it.

“Step three is jr” — wrong, and the entry had it right

The standup’s handoff said the next step was to add jr and watch the notation fail. Adding jr changed nothing at all. A relative jump the programmer wrote is two bytes whether it reaches or not, so layout sizes it without asking a single question — and the range check it does want turned out to be comfortable, because the walk after layout has every address.

What creates the problem is not the instruction but the choice: br, which is jr when the target is in reach and jp when it is not. ROADMAP 2.5 had said so in the sentence that opened it — choosing the short form shrinks the code — and the plan compressed that to the mnemonic and lost the verb.

A plan that names an instruction where the entry named a decision has dropped the part that was hard.

“One walk cannot decide a br” — over-stated, and the correction is the useful half

The expectation was that a single walk could say nothing about an encoding that depends on distances. It can say quite a lot: a thread of the labels already met is exactly what one walk has, so every backward br is decided exactly and only forward ones have to be assumed.

That is a sharper limit than the one predicted, and it is what made the second walk worth writing rather than a token gesture — half the problem was already solved, and the half that was left had a shape.

“The fix belongs at the end of read_one_pass” — right shape, wrong file

The thread-declaration wart was written up with its own fix attached: a sweep at the end of the pass reader, refusing a synthesised clause whose name is declared a thread further down. The sweep is exactly what was built. It is in check.c.

A pass is only whole once every %import has been read. A rule in one module and its thread in another is the same defect, and a check running at the end of each file would not see it — it would see a pass with no thread declared at all. The prediction was made from where the classification happens rather than from where the question can be answered, which is the same error as the two above at a smaller scale.

“Next is building the circular attribute” — and unrolling it by hand was better

This is the one that paid. 2.5 had its customer, its witness and a written condition that had been met, so the obvious move was to build the mechanism. What happened instead was one round of it written out by hand — relax, which is layout a second time — and it settled three things the mechanism would have had to guess at:

   
the direction shrink only. Every address in the previous table over-states, so a br that fits against it still fits when everything settles — and that is now checked, not argued: code refuses a br that shrank and no longer reaches
the step one pass exactly as written, with nothing added for iteration
the witness wrong. One round reached the minimum on chain.z80, so the file that had been the evidence stopped being evidence and a deeper one had to be built

That last row is the reason to write the workaround first. The witness a mechanism is justified by can be retired by the prototype of that mechanism, and finding that out after building the real thing would have meant a feature justified by a file that no longer needs it.

A workaround that is the mechanism run once by hand is a specification that executes. Building from it is cheaper than building from an argument, and it is the only version that can tell you the argument was about the wrong file.

And one that cost a cycle rather than a day

make test printed 206 passed, 1 failed and the records were updated to say

  1. Fixing the failure turned that check into a passing one and the answer was
  2. The number of checks a red run reports is not the number a green one will, which is obvious once written down and was not before.

16. The calling convention cost the most, and not for the reason given

ROADMAP 6.1 made three predictions on 2026-09-06. The first two are still open. The third was: the arm64 calling convention costs more than any construct before it, because it is the first place the emit pass has to know something the tree does not say.

Right on the cost, wrong on the cause. It took three rounds against the tool before the oracle ran, where the five constructs before it took one or none. But the convention itself, registers and pushes and the prologue’s stores, went in unchanged from the first draft and was sixteen lines. What cost the rounds:

   
C99 6.5.2.2 a call needs a declaration above it. The draft gathered a table on the way up, awk’s answer, and cc refused the oracle program that relied on it. Prototypes went into the grammar and the table became a thread
a rule with no action answers the one thing it matched, so one parameter arrived bare. -> $1 on the list rule
two rules for one pattern, and a check reading its own rule both refused by the tool at read time, both already answered in z80.phx

Only the first is about C, and none is about the emit pass knowing something the tree does not say. What the emit pass needed, an argument’s index and a parameter’s register, the tree was made to say: Arg and Param are nodes so that a pass can put an attribute on them. The prediction imagined the convention as knowledge outside the description; the description absorbed it by giving the tree two more node kinds.

A prediction about where the cost lands is worth scoring separately from one about how much. This one got the size right and the place wrong, and the place is the useful half: the expensive thing was a rule of C, met by the oracle, not a rule of the machine.

What the first two predictions look like from here. One, no change to the tool through struct: ten constructs in by the end of 2026-09-22, every item of ROADMAP 6.1 but struct itself, and nothing in phoenix/ has been touched. That day is the strongest evidence it has, because two of its changes were the ones most likely to have broken it: the value narrowed from sixty-four bits to thirty-two, and the frame stopped counting slots and started counting bytes. Neither wanted anything the notation did not have.

It came nearest to failing at string literals, and did not fail. The bytes of a literal cannot be computed in the notation, which has no iteration over data (ROADMAP 5), so the assembler writes them. That is a borrowing, recorded in ROADMAP 6.1’s list of what the arc borrows, and not a change to the tool; but it is the first construct whose meaning the description could not state in full, and a C compiler that someday has to write its own bytes will meet it again.

Scored the same evening: struct arrived without a change either, and § 19 closes the prediction.

Two, that the typedef predicate is the first change wanted: still neither confirmed nor threatened. The tool has spoken three times since, all at read time: twice about an attribute name defined by two passes in one driver, and once about an attribute given a field’s name, which it named with the line and the reason. All three were warnings about this description and not a need for a different tool.


17. “Left to the oracle” is not a decision until somebody writes the program

The 2026-09-21 entry left the eight-byte int open and said how it would close: an operation whose answer differs between the two widths is a program on which cc and this description disagree, and char is where conversions become defined, and that is where this gets decided. The 09-22 standup carried it forward unchanged under held on purpose.

Both halves were wrong, and differently.

The place was wrong by two constructs. sizeof comes before char in ROADMAP 6.1’s own list, and sizeof(int) is 4 under this cc where the description would answer 8. Finding it cost one command, aimed at the construct after the one being written rather than at the one in hand.

The deferral was worse. “Left to the oracle” reads as a decision to let evidence settle a question, and it is only that if somebody writes the program that puts it. Nobody had. The program is two lines and had been possible since the second construct:

int main() { int a = 2000000000; int b = a + a; return b / 1000000; }

cc exits 218, wrapping at thirty-two bits to -294967296 and dividing to -294. This description exits 160, keeping 4000000000 and dividing to 4000. Signed overflow is undefined, so neither is wrong by the standard and the 09-21 phrasing, no defined divergence exists yet, was exactly accurate. But accuracy was not the job: the sentence was guarding an open decision, and the disagreement it was waiting for had been sitting in the subset for a day. An oracle answers what it is asked, and fifty-eight programs had not asked.

A question handed to an oracle without the program that puts it is not deferred, it is dropped. The cure is the same habit that found the place: write the witness on the day the question is raised, not on the day the construct that forces it arrives.

Decided 2026-09-22: sizeof(int) is 4 and int narrows to thirty-two bits. A 64-bit int is a conforming implementation, the standard asking only for sixteen bits, so this was a choice between two legal answers rather than a defect to fix. It went the way it did because the other answer costs cc as the oracle for every size-shaped construct left in the arc. journal.md under that date has the reasoning and what the machine makes it cost.


18. What a silent breakage means

Since 2026-09-22 each construct in the C arc has been checked by leaving its steps out on purpose, one at a time, to see which oracle program goes red. It started with the narrowing of int and became the habit for everything after it. The belief it rests on was never written as one, so it is written here: a step that can be left out with the oracle still green is a step no program can observe.

That afternoon it was put to nineteen breakages across five steps, indexing, the pointer difference, char, character constants and string literals, and four came back green. One of the four was the apparatus, twice over.

silent breakage what the silence meant
no sxtb after a store to a char the apparatus, in two layers. The sed that was to remove it matched nothing, so the green was the unbroken file. And had it matched, the witness could not have seen the difference: char-assignment-value.c returned 300 as its exit status, the shell keeps eight bits, and both routes reached 44 because the shell did the narrowing the program was checking for
lsr for asr in a pointer difference unobservable, and truly. The two shifts differ only in bits an int does not keep. It becomes observable when the difference is a 64-bit ptrdiff_t
no mov w0, w0 after a pointer difference unobservable, as the comment written with it said
a char array sized as ints unobservable. It only makes the frame larger, which no program can see

Two more faults of the same kind turned up without a breakage to find them. A refusal written with zsh’s echo held a valid 'A' where '\x41' was meant. And a phx warning about an attribute named like a field was cut off by a | tail on the output, so a description that the tool had flagged looked clean. Neither the code nor the tool was wrong in any of the four. What was wrong each time was the route by which a result reached the person reading it.

So the belief held three times out of four, and the one failure was two faults of one kind. A silent breakage is a question: either the step is unobservable, or the thing doing the observing cannot see it. The second is checked first, because it is cheaper, by confirming that the breakage applied and that the witness can carry the value it is testing. The harness now asserts that each replacement applied before it runs anything, and a witness whose answer can exceed eight bits divides it before returning. Where a step is unobservable in truth, the reason is written at the instruction, so that the day it becomes observable the comment says which program will catch it.


19. Prediction one held, and the node went somewhere else

ROADMAP 6.1 predicted on 2026-09-21 that the C subset would reach struct with no change to the tool: %import, the symbol pass, thread for frame offsets and one emit pass would be enough. struct went in on 2026-09-22 and git diff e3c093d^ -- phoenix/, the whole C arc, is empty. Held, at eleven constructs of eleven.

It held more narrowly than the count suggests, and the ways it nearly did not are the useful half.

Member offsets were a thread, as frame offsets were. The prediction named thread for the frame, and the same mechanism laid out a struct: three threads reset by the definition, the offset so far, the widest alignment and the members, with padding worked out as ((off + a - 1) div a) * a. Nothing new. What the notation lacks, a fold over data, was not needed, because the members are nodes and a walk visits them.

The standup’s expectation about where a type becomes a node was wrong in place. It said, and the grammar’s own comment said, that struct is where a type stops fitting in numbers and becomes a node. The expression side stayed numbers: stars, width and a tag, three where there had been two, because a member’s type is looked up by the tag rather than carried. The node went to the base of a declaration, where five copies of a table keyed on the keyword became one Base node that looks up int, char and a tag in one table of layouts. That is the second time in a day that a comment predicting this construct will need a node was one step off (char was the first).

A prediction about what shape a data structure will need is worth writing at the construct, not ahead of it. Twice today the shape was right and the place was wrong, and the place is only visible from inside the change.

The tool spoke once, and was right. A later pass cannot read a thread, so the types pass could not see the table of layouts. The fix was the description’s: locals keeps the table at each Member node as an ordinary attribute. The first try gave that attribute the thread’s own name, which updates the thread rather than keeping a value at the node, and the types pass still could not see it. Renamed, it worked. Both are the notation doing what the manual says.

One instruction changed shape, and that is the emit pass’s business rather than the tool’s. A step through a pointer was a shift, because every width was 1, 4 or 8, and a struct of three ints is twelve. The step became smaddl, a sign-extending multiply-add, and the pointer difference an sdiv. That also gave the division its first witness: udiv differs from sdiv in the bits an int keeps only when the divisor is not a power of two, so the breakage that came back green under § 18 is caught now.

The breakages, fifteen, with the harness asserting that each applied. Thirteen went red. The two that stayed green were the load a struct’s value leaves out, at a name and at a *. Per § 18 the observer was checked first: under both breakages the assembly for all 143 programs was byte for byte the same, so nothing asked. The only thing that can ask a struct for its value is a statement that discards it, and that is written at the instruction.

Prediction two, the typedef predicate as the first change wanted, is now the next thing the arc tests, and nothing between here and typedef stands in the way.

20. Prediction two held, and the predicate was a table

The same entry predicted on 2026-09-21 that the first change the tool needs is the typedef predicate, and it is wanted at typedef and not before. typedef went in on 2026-09-23 with %names, and it was the first change to phoenix/ since the arc began. Held, on both halves.

Wanted at typedef: measured, not argued. Before a line of the tool was touched, a copy of c.phx let a declaration’s base be any name, which is the most a description can do alone, and all 143 programs were run through it. One moved: sizeof(x), with x a variable, became the size of a type named x and was refused. So the wall was real, the witness was already in the suite, and nothing before typedef had reached it.

The first change: a predicate, as named, and not only one. The toolchain document called it “an identifier rule that succeeds as a type name only if a scope stack maintained during the parse says so”. That sentence has two halves, and the prediction was about the first. The asking is one rule and ten lines of the matcher. The stack is the rest: something has to write to it from the declarations, end what a block wrote, and take back what a failed alternative wrote, and that is most of the work. It arrived as one directive naming node types, with no code in the grammar, which is the part the prediction did not say and the thesis did.

The notation was chosen, not found, and is written here so that it can be scored later. declare and hide name a node and a field; scope and guard name rules. The alternative, a binding written inside a production, would have been a second place where a declaration is described, and the tree already says which nodes are declarations.

The first version gave a wrong answer, and no test found it. It bound a name when the node naming it was finished, which for int T = sizeof(T); is after the initialiser: the second T was still the typedef of char, and the program answered 1 where cc answers 4. Every oracle program agreed, because none declared a name and used it before its own ;. It was found by writing the journal entry, while stating when a binding happens in C’s terms: C11 6.2.1p7 says the end of the declarator, and the implementation said the end of the declaration. The binding moved to the factor, and the program is now refused by the locals pass, which has the same off-by-one for every local (int x = sizeof(x); has always been refused). A wrong answer became a refusal, which is the trade this subset makes everywhere.

A mechanism that is about when has to be checked against the standard’s sentence about when, not only against programs. The oracle can only answer the questions somebody wrote a program for.

The breakages, eleven, each asserted applied. Four in the matcher, no undo, no scope, no guard and binding at the node, each red on the grammar written to test it. Seven in the description, and all seven red. But two programs named for the hiding they tested, a parameter and a local of the typedef’s own name, stayed green with the hiding removed, while others went red under it: nothing in either asked a question whose answer depended on it, because return T * 2 begins with return and no reading of it declares anything. Both were rewritten to ask sizeof(T), where the variable and the type differ, and both go red now. That is § 18 again, one level down: a program named for a property is not a witness to it until the property is taken away and the program notices.

And the observer, once more. The first run of the matcher breakages said that restoring the source had not restored the behaviour. It had. make compared timestamps within one second, saw the binary as new as the source it had just been copied back from, and did not rebuild, so the “restored” line was the previous breakage still running. make -B gave the answer the source said. A harness that edits and rebuilds inside a second has to force the build, or its results describe the build before.

size_t and ptrdiff_t were revisited, as promised, and kept. typedef gives C a way to name them and gives this subset nothing to name them as: both are longs on this machine, and long is step four. The two divergent programs stay pinned with both answers.

What prediction three said about the calling convention is scored in § 16. The arc has now used all three, and the next change to the tool is not predicted by anything written down.

21. “What is missing is now only the word for it”

ROADMAP 2.5 said so on 2026-09-06, after relax had been written out once by hand, and set down five parts as settled by that prototype: the step is one pass exactly as written, the bottom is layout’s table, the direction is shrink only, the test is did any size change since the last round, and the guard is a bound with a diagnosis. It also leaned one way on the open question, where the repetition is written: the driver, because a pass that iterates would be the interpreter 3.1 refuses. The driver was chosen on 2026-09-23 and built the same day. Held, on four of the five parts and on the lean, and the fifth was wrong in a useful way.

The step, the bottom, the direction and the guard went in as written. relax changed by one line: it defines labels rather than labels2, so that the round can be compared under one name, and the two passes after it read labels too. The runtime is one loop in run.c around a stage it already ran.

The test was the wrong thing to compare. “Did any size change” is a question about the output, and what a round depends on is its input, the label table it is handed. A br after the last label can shrink without moving a label, and the next round makes the same decisions from the same table, so the table is the fixpoint and the sizes are a consequence of it. The driver compares labels, with the structural = a clause has.

“Only the word” was nearly true, and the rest was in the checks. Nothing about how a pass runs changed. What changed was two read-time checks that were right about one walk and wrong about several: a down clause reading what its own rule computes, which in an iterated pass is last round’s answer, and two passes defining one attribute, which here is the start and the step. Each now waives exactly the attribute a driver names after until, and four new refusals say what until cannot mean. Then the notation’s own description, languages/phx/phoenix.phx, had to learn the word, and it was the suite that said so, by failing to parse the new z80.phx.

The witness needed deepening before it could witness. two-rounds.z80 needs three walks, and a fixed three would pass it. three-rounds.z80 needs four and was worked out by hand, three nested brs each brought into range by the shrinking of the one inside it, then simulated round by round against relax’s own rule before the tool ran it, and the two agreed at every walk: 134, 133, 132, 131, 131.

A prototype that is the mechanism run once settles what the mechanism computes. It does not settle what the checks around it believe about one walk, and that is where the surprises were.


22. “A per-program limit that reports did not finish”

The shape of the fix for a suite with no timeout was written down on 2026-09-23, in that day’s standup, after a breakage witness had run for two hours and forty minutes: a limit on each program the harnesses run, and “did not finish” reported as a failure. The journal of the same day added the claim behind it, that a witness which hangs makes make test hang rather than fail. Built on 2026-09-24. Held as a shape, and it was half of the fix.

The claim held, and understated. A C while made endless hung the oracle as predicted; under a limit it failed in four seconds with the program named. But calc’s while made endless did something the prediction had no word for. The loop was around a print, and the shell capturing it ran out of memory within the twenty seconds and ended the run with no report, which is worse than a hang. “Did not finish” has two ways to be true, running too long and writing too much, and a loop in a language whose programs print will usually be the second. The limit counts output too.

“Reports it as a failure” was the part that needed the most, because a stop can look like a pass. A check that expects a refusal and discards standard error takes a stopped program for the refusal it wanted, and an oracle whose two programs are both stopped at the same point sees them agree. Neither is visible from inside the check. Every stop is now logged, and one check at the end of the run fails if the log has anything in it. Its first run found the second case in earnest: the two sides of a 49 MB solvm --trace comparison had been cut at 16 MB and matched.

And the limit was an instrument, so it was wrong first. The suite went from 287 seconds to 533 with every check still passing, and the first two explanations, a 50 ms wait and then a busy machine, were wrong. A program could be reaped in the same pass as its pipes closed, and the pass then slept until the deadline before reporting it finished. It was found by timing every line of the old suite and the new side by side, and seeing gaps of exactly the limit before lines reading ok.

So § 18’s rule held three more times in one day: when a result looks wrong, the observer is checked before the subject. Each of the three faults here was in the observer, and none was found by a check, because a check that cannot finish, or a harness that finishes late, is the thing that cannot report itself. What found them was a breakage run, a timing, and a log.

A harness that can only pass or hang has two answers, and one of them looks like a slow machine. Give it a third, and then check that the third is not being mistaken for the first.


23. “Whether the emit pass needs anything is for the oracle to say”

ROADMAP 6.2 was written on 2026-09-25 before any of it was built, and said, of a function returning a pointer: a pointer is not wide, which is kept for long, so the return template may already leave all of x0 alone, and that is a prediction, not a finding. Built the same day. Held for the case it was about, and missed the case beside it.

A plain pointer needed nothing in the emit pass, as predicted: the return template narrows only a long into an int and widens only the reverse, and a pointer is neither.

A pointer to a struct needed three guards, and the prediction had not thought of it as a separate case. It has the struct’s tag, and three places, the types pass’s question of what a return must give, and the emit pass’s questions of whether a call’s result is copied into the frame and whether a return copies bytes out, asked “has a tag” to mean “is a struct”. Each failed struct-pointer-returned.c when put back: a refusal, a wrong exit status and a crash.

What the prediction got right was its own last clause. It deferred to the oracle, and it was the oracle that found the three.

A new kind of value passes through every question the old kinds answered, and some of those questions were asking something narrower than they said.


24. “x0 always holds the zero-extended w0”

The header of languages/c/c-arm64.phx has said so since 2026-09-22, when int became thirty-two bits: every write to a w register zeroes the upper half, so an int in x0 is zero-extended and a push of either width is lossless. Every condition in the emit pass tests all of x0 on the strength of it. True of everything the file emits, and false of the one value it does not emit: an int returned by a function another compiler built.

AAPCS64 leaves the upper half of a returned 32-bit value unspecified, and cc uses the freedom even at -O0: int lnarrow(long x) { return x; } returns the whole long. if (lnarrow(4294967296)) was taken here and not under cc. The fix is one mov w0, w0 after a call that returns an int.

The claim was scoped wrong, not reasoned wrong. Its argument was about instructions, and every instruction the file writes keeps it. What it could not see was a value that arrives from outside the file, and the only place that happens is a call. tests/abi/ was built on 2026-09-23 to link the two compilers’ code together, and had the shape to find this from the start; it never tested more than the low half of what came back, so it asked nothing.

It was found by reading the promise against the convention, while building !, && and ||, which lean on it hardest, and confirmed by a pair of files, one compiled by each compiler. It is the first entry in the defects table of COMPLETED.md that reading found, and the reading was the hypothesis; the pair was the finding.

An invariant stated about the code is only as wide as the code. Where a value comes from somewhere else, the invariant is a claim about somebody else’s code, and it has to be tested there.


25. “Which way that copy runs is the part to get right”

ROADMAP 6.6 was written on 2026-09-26 before any of it was built, and said of the extra arguments to a variadic call: the emit pass loads the named arguments into registers as today, then moves the rest from their sixteen-byte pushes into eight-byte slots at the bottom of the area, in order, and raises sp by what is left, and that the direction of the copy was the part to get right. Failed: there is no direction.

Worked through for ten extras before a line was written, the first extra’s slot is where the fourth was pushed, so an upward copy overwrites a push it has not read; and downward, the seventh’s slot is where the first was pushed. The slots have to go somewhere the pushes are not, and the only such place is below them, which moves the add sp from before the bl to after it. A call with no extras is emitted as it always was.

What the prediction got right was naming the risk. It picked out the one step that could go wrong and said so, and working that step by hand, on the largest case a witness would hold, was what refused it.

A plan that names its hard part has told you where to do the arithmetic before the code. Do it there.


26. “The first thing to try is void as a fourth such entry”

ROADMAP 6.7 predicted, from the table of layouts holding int, char and long as structs with no members, that a void value would be refused wherever a struct is refused where C wants a number, for nothing, and that only the messages need to learn to say void. It added that the emit pass would have to keep a void call from getting a struct’s temporary. Held, and cost more than the messages.

The mechanism held exactly: thirteen refusals, each saying one thing once, through checks written for structs. The messages were about twenty-five, all printing 'struct {}' of a tag, and now print a kind. And the temporary was one of three places that asked “is a record” and meant “is a struct in memory”: a call’s temporary, which it predicted, ?: and a member, which it did not. It is postmortem 23’s shape again: a new kind of value passes through every question the old kinds answered, and some of them were asking something narrower than they said.

Reusing a mechanism is cheap where the mechanism is general and costs exactly where its questions were worded for the old case.


27. “Nothing can see the difference yet”

Two claims said no program could show a thing. Both were wrong, and each was refuted by a program of one line.

The comment on SizeOfType in c.phx, since 2026-09-23: sizeof is a long here and an unsigned long in C, and nothing can see the difference yet: a size is never negative, and unsigned arithmetic is what would show it. sizeof(int) - 5 < 0 is unsigned arithmetic written without the word. cc answers 0 and Phoenix 1, and sizeof-is-unsigned.c differs in all five of its values. The oracle had never held a program that subtracts past a sizeof, so for three days its none diverges was a fact about the programs it held.

ROADMAP 6.8, 2026-09-26: folding a long would change nothing case can show, since a long label in an int switch is already refused as too wide. True of an int switch, and a switch on a long takes wide labels: case 4294967296 * 2: had been refused there, and long-case-labels.c holds eight such labels.

The claims of the same kind that have held are void’s width, today, and the zeroing of a small struct’s register slot, since 2026-09-23, and they hold for a reason the two above lacked: every reader of the thing was listed, and each was refused or could not look. void’s width is read by arithmetic, * and sizeof, and all three refuse a void. The sizeof comment named its reader, unsigned arithmetic, and did not ask whether an expression could be unsigned without saying so.

“Cannot be witnessed” is a prediction like any other. Before writing it, list every reader of the thing, and try to write the program for each.


28. “The site cannot fall behind the documentation”

The journal of 2026-09-03 said so of the website: pages.yml assembles it from docs/ on every push that touches them, and nothing is committed built. Failed, for three weeks, without a word to anyone reading the repository.

The claim was about where the pages come from, and was true of that. What it did not cover was the build failing. On 2026-09-05 the site’s description in www/_config.yml was rewritten to say Phoenix writes both down: EBNF, and in an unquoted YAML value a colon and a space begin a key, so the file stopped parsing. Every run after it, fifty-five, failed before Jekyll read a page, and GitHub Pages went on serving the last site that had built. A site built on every push cannot fall behind only while every push builds; a failed build freezes it exactly as a committed one would, and a committed one at least shows its age in the history.

Nothing here looked. make test parses nothing under www/, and the workflow’s result is on GitHub, where it was found on 2026-09-26 because Hans asked why the workflow complained. The fix is one pair of quotes; the lesson is where the check was not.

“It is rebuilt from source” is a claim about the success path. Its failure path has to be watched by something, or it is a claim about the last time it worked.


29. “unsigned-constants.c is the witness”

ROADMAP 6.9 named one program for each of its three parts, written before the part, from what the part was for. Each agreed with cc when its part was built, and each let breaks through: five of twenty-four in part 1, two of six in part 2, and fourteen of twenty-eight in part 3, where unsigned-constants.c agreed with fourteen broken compilers.

It was written to show what breaks without the part, and did: it could not be compiled at all before, and every line of it is a thing the part adds. What it did not do is look where the part could be wrong. The part 3 witness has hex, suffixes and two unsigned switches, and no constant expression that wraps, widens or multiplies past 2^63, which is where the constants pass does its work. The part 1 witness had no unsigned int meeting a long, which is where signed and unsigned differ.

The two predictions about what could not be seen held. Part 1 said a switch on an unsigned int sign-extended differs only against a negative label, refused until part 3; part 3’s labels saw it at once. And the rule that an unsigned long folds only below 2^63 held on both days it was used, a day earlier than planned, because the notation’s integers are signed and a comparison was the only operator worth an exception.

A witness written from what a feature is for shows that it exists. The program that shows it is right is written from how it could be wrong, after the code, by breaking it.


30. “A type gains a fifth part, which of its levels are const”

ROADMAP 6.10 was written on 2026-09-29 before any of it was built, and made five predictions a reader can check against COMPLETED 6.10.

Failed: the fifth part as a number. “Bit 0 the value itself, bit 1 what it points at, and so on, so a * halves it and an & doubles it.” A declaration’s stars reach a pass as a list, and the notation can make text of a list and no number, so the levels are text. The design survived and the representation did not; nothing in the entry had asked what the notation could fold.

Held: nothing in the emit pass changes for const. It did not.

Held: the risk of static is in what it leaves out. The entry said a zero global written as a common symbol would link, merge with another file’s, and print something else, and that the emit pass writes none. The link test was built for exactly that, and the emit pass was checked, not assumed, before the sentence was written.

Failed, quietly: a static local’s initialiser “is a global’s”. It is, and so were its messages, and one of them was untrue of a case no global could reach: the address of a local, refused as a limit of this compiler when C says it is no constant. It was found by writing the refusals, the same way the morning’s fix found three refusals nothing asserted: a message is a claim, and only a program that reaches it tests it.

Held, narrowly: const twice “accepted as cc accepts it”. The first grammar took one const on each side of a base and would have refused const const int. It was caught because the entry had said it, which is the case for writing an entry’s promises down before the build.

And 29’s lesson was tried three times in one day, with a difference. 29 said the program that shows a part is right is written from how it could be wrong, after the code, by breaking it. Parts 1 and 2 did that and let one break and then three through, each needing a witness afterwards. Part 3 read its own plan for the places a pointer’s type reaches a place, wrote the eight witnesses the plan had no program for before any break, and let none of thirty-one through.

How a part could be wrong can be read off its design before the code runs. Breaking the code afterwards is how to find out whether that reading missed anything, and it is cheaper when it finds nothing.

31. “The notation has no fold over a list and no way to take one’s tail”

ROADMAP 6.12 was written on 2026-09-29 and 6.13 on 2026-09-30, each before its hard part was built, and both named a capability the notation lacks as the thing their hard part would turn on. Four claims can be checked against COMPLETED 6.12 and ROADMAP 6.13.

Held: the way that stays inside the notation, “where the tree gives the recursion a fold would”. Part 4’s entry named two ways and guessed the first, and both were prototyped on arrays.c before either was built: forty of forty each, and text was kept. The tree did more than the entry said. A declarator’s brackets had been read as a list, and became a nested chain of nodes, so the size of each is an attribute of the one inside it and no size is ever a product.

Missed: what it would cost. The entry asked how a type is spelled; the work was in when it is known. A value handed down is worked out as its node is entered, so (*p)[3]’s brackets had to reach the declarator inside the parentheses as nodes read later, which works only because a node’s children are walked in the order its fields are written. Nothing in the entry looked there.

Held: “the notation cannot sort.” It cannot, and nothing needed it to. A global whose designators go back is written as zeros and filled by a routine run before main, in the order the values are written, which is also the order C says the last of two wins. The question assumed the data had to be written in address order by the compiler; only the bytes at start-up have to be in place.

Failed, then held: a struct’s members “with their braces and without”. Part 2 could not do without: finding the scalar after the last one in a struct looked like a walk down and back up its members, a loop, and it was refused by name and put off. Part 3 found it is a lookup if each struct carries a map of its bytes, one letter a byte naming its member, padding counted as the next one’s; the descent is then a fixed number of steps. Twenty answers of twenty against cc’s offsets, before anything was built.

Not a prediction, and worth recording beside them: nothing in 6.1 said that a local is always reachable as [x29, #-offset] or a member as an immediate add, and both were believed by every program the oracle held. A 320-byte array and a 20,000-byte struct, each written to witness something else, showed that arm64 has neither form past 255 and 4095.

Three times in two days a part looked as if it needed iteration the notation deliberately lacks, and three times it did not: the tree was the fold, start-up was the sort, and a map of bytes was the walk. ROADMAP 3.4’s line held because each question was asked again in the terms the notation does have before the tool was changed, and asking it again was cheaper each time than the change would have been.

32. “It is settled with the first witness in hand, by what each costs in the passes”

ROADMAP 6.14 and 6.15 were written on 2026-10-01, each in the morning of the day it closed, and each named its hard part before building it. Six claims can be checked against COMPLETED 6.14 and COMPLETED 6.15.

Failed: “a function becomes a level of its own”, f:1:0:1:KEY. Every clause that works a type’s stars out of its text finds the first a: or b: and divides by eight, so a new kind of level would have been counted as a pointer in all of them. A function became a base with its key in a sixth field, and those clauses were right about it unchanged. The entry had asked how a function is spelled and not what already reads the spelling, which is 31’s miss again: there it was when a type is known, here it was who counts it.

Held: “KEY names its parameters in the tables a call already reads.” A call through a pointer reads the four tables with the pointer’s key where a direct call reads them with the callee’s name, and every struct, long and variadic rule a call had came with it.

Held, and measured by something it did not name: one call node or two, “by what each costs in the passes”. Both were prototyped, and the passes did tell them apart, 143 lines to 97. What decided it was not in the passes: a call by name had been parsed again by each alternative of suffix that started with it, so nested calls multiplied, and one node took the oracle from twenty seconds to nine where two took it to thirty-four. Prototyping both was what found it; reasoning about the passes would not have.

Held, and smaller: part 3, “a function’s address as data”, was one clause. The entry gave Proem’s shape a part of its own. Members and calls through them had come with parts 1 and 2; what was left was counting a function’s name as an address worked out before the program runs.

Held: _Bool’s question, “each of those places converts today by narrowing, and each must learn the one conversion that does not”. Nine places did. The entry did not say how a place would know, and that was the work: a _Bool’s text was an unsigned char’s, and a seventh field was added that no other type has.

Failed: an enumeration constant is “an int the constants pass works out”. The same entry, a paragraph above, had recorded from probing cc that an enum with no negative constant is an unsigned int. That makes an object’s type depend on its constants’ values, and the types pass, which needs the type, runs before the constants pass. Settled with Hans: enumerators are worked out where they are declared, the one place a constant expression is worked out early, and a narrow bound on what it works out had to be found by the suite, since it runs at every Binary.

A plan here names the right mechanism and misses where it costs, and every miss today was a reader of the same data the plan did not count: clauses that read a type’s text, alternatives that re-parse a primary, a pass that runs before the one that knows. What the entry says about a new representation is checked by asking what already reads the old one.

33. “Each is worked down to one marker by substitution, a digit at a time”

ROADMAP 6.16 was written on the morning of 2026-10-02 and closed the same day, and the journal of 2026-09-22 had said two things about it in advance. Five claims can be checked against COMPLETED 6.16.

Held: \0 in a string “is refused rather than miscounted, and arrives with the octal escapes” (journal, 2026-09-22). It arrived with them and not before, and "\01" is one byte.

Held, and not as framed: “whether this wants a library function is not settled by one use” (journal, 2026-09-22), with ord as the candidate and octal and hex escapes, the morning of this entry, called the stronger argument for it. No library function was added. A character constant’s code from its digits is int(text, 8) or int(text, 16), which the library had; a string’s length is a chain of substitutions, which works only because the digits are bounded, octal at three by C and hex at two by a refusal by name. So the second use did not come, and the bound is what kept it away: a hex escape C allows, zeros before two digits, is refused to keep the chain finite. The argument for an ord, or for a loop over text, is unchanged by this entry rather than strengthened.

Held: part 1 “as small as planned”, and the assembler as the entry found it. as was asked one escape at a time before anything relied on it, and refused \v, \a and \? as the entry wrote; three rewrites covered them.

Held, and larger: part 2, “each piece written as an .ascii of its own”. The pieces did need to stay apart, and the witness’s "\1" "2" showed it once part 3 was in. Two things the entry did not name: the NUL had to move from .asciz to a .byte after the last piece, and the quote of a \" before a join had to be set aside so a join is not found inside it, which the emit pass needs and, as a break showed, the length does not. _Static_assert’s message is a string too, and stopped on a joined one until it took them.

Held in mechanism, failed in the first writing: “the most an escape can have being three”. The substitution worked, and the __TEXT,__const placement it planned was right, as a break that undid it showed with "\08"[1] reading 1. But the first hex stage counted from \x, so the marker taken for a third digit stood for the second and the witness’s own \x41 was refused. And the breaks found digits no witness wrote: a stage missing one digit went unseen until the witness was generated, as char-constant-every-one.c had been for the table of codes.

A chain of substitutions has as many places to be wrong as it has links, and a hand-written witness tests the links its author thought of. Where the description enumerates, the witness should enumerate the same thing, generated, so that cc judges every link and not a sample.

34. “A refusal and not a wrong answer, and so left”

On the afternoon of 2026-10-02 Proem’s source was run through the check driver for the first time, where every entry before had been chosen by counting words in it. Three things written earlier met that run.

Held: “CPP’s lexer.c and source.c stop there”, the map’s line for the escapes, written on 2026-09-29. Phoenix as it stood before 6.16, run on the same preprocessed files, stops lexer.c at '\v' and source.c at '\r'. It held for a reason the line did not give: both files have stops earlier in their text, an unsigned char return and a name in its own initialiser, and neither is reached first, because the lexer reads the whole file before any pass runs. A probe’s first stop is the first in the earliest phase that refuses, not the first by line, so a lexical stop hides every later one in its file, and a table of first stops says less about the passes than it seems to.

Failed, as a judgement of what could wait: a name in its own initialiser, “a refusal and not a wrong answer, and so left” (c.phx, 2026-09-23, and COMPLETED the same day). It was the commonest first stop in Proem, four files of ten and thirteen uses, and it was on no list, because a refusal that costs nothing in the oracle costs nothing in any count the project kept. It cost the first real program it met.

Held: “It is fixed first” (ROADMAP, the same afternoon). It was fixed the same day for a plain local, every form Proem writes, and three of the four files it stopped are accepted whole; the fourth stops at ?: with a null pointer constant, already listed.

A count of words in a program says what it uses; a run says what it stops at, in the order the compiler meets it. Where the target is a real program, run it, and read each stop knowing which phase made it.

35. “Whether a thing is a constant 0 is the constants pass’s answer, which runs after it”

On 2026-10-03 the next two of Proem’s stops were lifted, and three things written earlier met them.

Failed, as a reason that outlived itself: the null pointer constant “stays out as well: c ? p : 0 is still refused, because the check is in types, and whether a thing is a constant 0 is the constants pass’s answer, which runs after it” (COMPLETED 6.7, 2026-09-26). It was true the day it was written. On 2026-10-01 enum needed an enumerator’s value before types, and the locals pass began working out ev and eok, which is the question C11 6.3.2.3p3 asks, in the pass before the check. Nothing reread the 6.7 sentence, and the fix, when it came, was one default attribute. A reason recorded for holding something out is a prediction that the obstacle stays, and it is not rescored when the obstacle goes.

Held: “a function returning a char stops lexer.c” (ROADMAP, the second run of 2026-10-02). It did, and lifting it took one row in a table short had built: the refusal’s own reason, “a register nothing here narrows”, had stopped being true on 2026-10-01 too, the same shape as the first.

Half held: “a probe finds only the first stop in each file, so what is behind each is found when it is lifted” (ROADMAP, 2026-10-02). Behind ?: with NULL in output.c and source.c there was nothing: both are accepted whole. Behind the char return in lexer.c there was another ?:, a void * beside a char array, 364 lines on. The line said only that something might be there, and that is all it could say. Behind that ?:, lifted after the first closeout, there was nothing: lexer.c is accepted whole, eight files of ten.

Held: the two uncaught breaks “may be breaks no program can see” (journal, the first closeout of 2026-10-03). They were: a function never has a void tag, and the answer of a refused ?: is read by nothing, so each guard was taken out rather than given a witness. The same journal paragraph’s other belief, that the const void * witness “has to be rewritten”, failed: it still told the two cases apart, and only its message moved.

A refusal’s reason is a claim about the rest of the compiler. When the compiler changes, the reasons written against it are the first thing to reread, before the stops a probe finds.

36. “A refusal missing, never a false one”

On 2026-10-04 seven of the open fixes were taken, easiest first, and four things written earlier met them.

Failed: the function-pointer check “is made at an assignment and an initialiser, and not yet at an argument or a return, where a pointer to a function of another type is accepted that cc refuses, a refusal missing and never a false one” (COMPLETED 6.14, 2026-10-01). The missing half was missing, but the half that existed was not only a refusal: copied to an argument, it stopped Proem’s output.c at proem_pp_on_file(pp, NULL, NULL) on the first probe, and the same clause had refused (void *)0 at =, a local’s initialiser and braces since it was written. No program had given a pointer to a function a null cast to void *, and C11 6.3.2.3p3 makes that a null pointer constant. “Never a false one” was a claim about the programs tried, not about the check, and a copy carried the defect to a place where a real program met it.

Failed, as wrong code: an enumerator’s value “in exact integers” (c.phx, locals, 2026-10-01). The comment bounded what exact integers could not do, a product past 2^62, a shift past 31, a division by zero, and said nothing of what C does differently from them, which is convert beside an unsigned operand. enum { A = -1 / 2u } compiled to 0 where cc gives 2147483647. It was found by a probe for something else, the ?: item, written to see what ev answered beside a u. Hans chose a refusal by name over unsigned arithmetic; every use in Proem, 1u << 0 and |, is one where the two agree.

Held: the fixes “sorted by what each costs, easiest first” (journal, 2026-10-03). The bottom three were a moved rule, a generated witness and a copied check. The middle ones, int x, f(void); and binop add;, were each a node or a table and an afternoon’s breaks, more than “a new binding or check on an existing pattern” said, but in that order. (), at the top, took a parse alternative, a flag through four passes and the emit’s register placement, and found a hole in the arity count on the way. The const levels of ?: and the notation’s limits are still there, untouched, as the sort predicted.

Half held: “a break that no test catches deserves the same doubt in reverse: … the right move was to take it out” (standup, 2026-10-03). Twenty-two breaks were missed on a first run today, and they were three different things. Five were guards no program reaches: npc at a global’s and a static’s initialiser and eok on - and ~ of an unsigned came out, and one frame count was kept, with a comment, because a right count is still the right count. Thirteen were witnesses too kind: puts, with one argument, showing nothing of the registers, or a definition further down binding what a declaration alone should have. Four were two holes: the shift clause asked about a negative left side, which only a child already marked can be, instead of a value past 2^32; and three misses on FnDecl were one fault in the arity count, which an unprototyped name let a mismatch through. Taking out each unreachable guard was right. Reading every miss as an unreachable guard would have deleted the fixes for both holes.

A missed break is a question with three answers: a guard nothing reaches, a witness that is too kind, or a check that asks the wrong thing. Which one it is gets settled by writing the program that should have failed, never by deciding first.

37. “The notation has no direct way to combine the two arms’ levels”

The same day was closed out twice. After the first closeout the last five fixes of the standup were taken in order, and then va_list, and four beliefs met them.

Failed, as a reason: ?: keeps the first arm’s const levels because “the notation has no direct way to combine the two arms’ levels a character at a time” (ROADMAP’s expressions row, 2026-10-03). That was true, and it was not the obstacle. Two arms of one type have one number of levels, and a type here has at most twelve stars and a base, so the answer is sixteen positions written out with slice, which gives nothing past the end and cannot trap. It took three breaks. A missing operator is not a missing answer when the input is bounded, and every input this compiler reads a level text of is. It is the shape of § 35 again: a reason for holding something out, rescored only when somebody tries.

Failed in part: the fixes’ order, “easiest first”, with the const levels of ?: near the end (journal, 2026-10-03; § 36 counted it as held so far). It was the smallest of the six: one clause, no new node, no new thread. The fourth, functions in a block, was the largest, and was sorted as a new binding on an existing pattern. The sort read the difficulty of a fix from how it was described, and the descriptions were written by what each fix touched in C, not in the description.

Failed, as an invariant nobody wrote down where it was relied on: “no value with a pointer’s type is settled” (c.phx, the constants pass’s note on why, 2026-09-29). It was written to explain why one check had no witness. Four other clauses relied on it without saying so: !, &&, || and the condition of ?: took any settled value as an integer. Settling a cast to a pointer, the first fix, would have folded int x = !(void *)0;. It was caught before it shipped only because the note said what was true and was reread. A comment that states an invariant is worth more than the one check it was written for, and the fix was to give each reader the question it had been skipping.

Half held: “a variadic function defined: the last stop in Proem, in expr.c and pp.c” (standup, closing the first time). expr.c was accepted whole the first time the builtins ran. pp.c went 644 lines further, to a ?: between two structs as a member’s value in braces, which the same standup did not list because a probe finds only the first stop. Nine of ten.

A reason for holding something out, and an invariant written to excuse one missing check, are both claims about the rest of the compiler. Reread them when the compiler changes: the first may have gone, and the second may be holding up more than it says.

38. “Refused by name: its type is the Paren’s, worked out after the early entry”

Four fixes from the standup of 2026-10-04, done in a session on 2026-10-05, and three beliefs about them met the code.

Failed, as a reason: a name in its own initialiser with a * in parentheses is refused because “its type is the Paren’s, worked out after the early entry” (standup, 2026-10-04; c.phx’s note on LocalInit, 2026-10-02). True of the declaration, which makes its entry on the way in and cannot read a child. Not true of the walk. The Paren is a sibling walked before the initialiser, and env is a thread, so the Paren can make the entry on its own way out. The fix was a thread handing it the fields it lacked. A down clause cannot read a child, but a thread runs through the siblings in order, and that is the way across. § 37’s lesson again: a reason for holding a thing out is rescored only when somebody tries.

Failed, as an implied fix: a function declared in a block is known after the block “since funcs has no block scope” (standup, 2026-10-04). The diagnosis was right, and the remedy it pointed at, putting funcs back at the block’s end as env is, would have broken every call of a function declared only in a block. The types pass reads the locals pass’s tables as they end, $allfuncs and the four beside it. What had the block’s scope already was env, which Variable asks first, so the declaration binds the function’s entry there, and a new table, fblock, says which names funcs must not answer for. A table that a later pass reads whole cannot be scoped; the scope goes in the table it is read through.

Failed, as a check: every break caught (this session, the first round on the second fix). Every one failed because $O, holding run.sh and the program’s name, was one word to zsh, so each break ran a command that did not exist. It was seen only because two of the breaks might have failed for a reason of their own, and their output was read. Run again, five went through: two found code the fix did not need, the ownname change and a reset of pown, which came out; one found a reset that needed a witness, which it got; two have none and say so. The fourth fix’s round found the same kind of thing honestly: a fallback in the fblock lookup that no input can reach. A break counts only if its failure is the program’s answer. A harness that cannot run reports every break caught.

Failed: the three *Agg nodes need the early entry too (this session). They were given it, and the oracle program could not be written, because no declarator with a * in parentheses takes braces here unless it is an array, which is still refused. The change came out before it was committed. The six nodes were patched by pattern, from what each held, and not from what the grammar lets reach it.

A reason, an implied remedy and a check’s green are three kinds of claim, and today each was wrong in a way the claim alone could not show. Read the failure a break produced before counting it.

39. “The assumption in the design most likely to be wrong”

ROADMAP 3.5’s own words for “there is no if”, and § 1 above scored it Held after the bytecode backend and again after awk. Rescored on 2026-10-10, when it was built.

Failed, at scale: tables-as-conditionals in place of an if. The § 1 score was right about what it measured. No backend found a choice it could not write, and none ever did: lookup([[true, a]], c, b) says every choice that does not need an arm left unworked. What § 1 did not measure was what the idiom costs when it is the commonest thing in a file, because no description had made it that until C. c.phx held 876 such tables on 418 lines, nested, each reading answer, condition, alternative. Asked whether the notation could use a struct, a map or mutation, the question went to c.phx for evidence, and the count was the first thing it answered.

Failed, as a forecast of the form: “the first thing it cannot express”. The entry expected a construct the notation could not say. What came was a construct it could say at a price nobody counted, and a gap that is only a price never meets a failure to announce it. A count is a way a design decision arrives, and it is a kind ROADMAP 3.4’s rule cannot see, since that rule asks only whether a thing can be written.

Failed, as a diagnosis made the same afternoon: that a pass could not build a record. Offered as the second gap behind c.phx’s types as text, a:24:2:4|b:4:0:4. A five-line probe built Ty(kind: "int", size: 3) in a clause and read $t.size back. The journal of 2026-09-30 had already said why the types are text, and it was 3.4’s rule choosing text over a list that read better, not a missing mechanism. Read the decision record before naming a gap, and probe before proposing to build.

Both failures have the same root: a claim about what the notation can say, made from the shape of the code that uses it. The idiom in c.phx looked like a workaround, and was one for one reason and not the other.

40. “Every obvious fix was a second mechanism”

COMPLETED 1.3’s words for sharing a computation, answered with otherwise, the general clause about a node. Rescored on 2026-10-10, with ROADMAP 3.4’s rule for the library beside it, because the day tested both.

Failed, as a boundary: the general clause was the whole answer. It was the answer for what 1.3 measured, an attribute every node has. It never reached a computation about a value handed in rather than the node it is on, and c.phx wrote one such computation 1,110 times in four spellings before anything could name it. %define is the second mechanism 1.3 was afraid of. What kept it from being the danger 1.3 meant is one rule, that the body sees only its parameters: a define that could read its caller’s fields would have been a macro, meaning something different at each call. COMPLETED 1.9 has the measurements.

Held, and not enough: ROADMAP 3.4’s “a pass needed it, and it could not be written in the notation.” repeat and find both met it, and both were added. But the rule asks only whether a thing can be written, and both had been written: as a thirteen-row table pasted 65 times, and as split and count. The table stopped at twelve and refused a valid program with a false message, which no one had recorded. What a workaround costs is not a question the rule asks, and today it was a silent bound and a tenth of a file.

Failed, as documentation: two read-time checks the reference listed did not exist. A template’s holes and a call’s arguments were checked only when a node reached them. Each was found by a rewrite that should have been harmless, not by reading the table that claimed them. Both are true now; COMPLETED’s list of defects has the rows.

A rule about when to add is scored by what it let in. The ones that let in nothing were never tested, and the price of a workaround it accepts is paid somewhere the rule does not look.

41. “A pass that adds one step of reachability, run until the table settles, would now say it”

ROADMAP § 5’s words for A -> B -> C -> A in languages/units/, written when 2.5 was built for the Z80 and the units customer was left unconverted. Scored on 2026-10-10 with the claim beside it, from lineage.md and COMPLETED 2.5, that the units’ two divergences were reachability and topological sort, the same missing thing twice.

Held, with a condition nobody wrote: the table could not be a list. The pass is the one the sentence describes, and it refuses the cycle in fpc’s words. But what a unit reaches, kept as a list of names, gains a duplicate every round on a cycle and never comes out equal to the round before, and the library has no way to say each name once. It works because each unit is one bit, and a bit set cannot grow. The sentence was right about the mechanism and silent about the value it iterates, which is where the work was.

Failed, as a diagnosis: “topological sort”. fpc’s initialisation order, asked of fpc on twelve graphs before anything was written, is a depth-first walk from the program in which a unit already entered is not entered again, and on an implementation cycle that rule, not any topological order, decides who goes first. Nor is it a fixpoint in JastAdd’s sense. It was built as a state stepped one move per node, with until used as a loop that stops when a finished walk steps to itself. The session’s own forecast that morning, a pass run until each unit’s rank settles, would have computed an order fpc does not use.

Held: “the same missing thing twice”. Both were answered by until and nothing else, on the same day, with no change to the tool.

A forecast of a mechanism is scored by what the mechanism is asked to carry. Both were right that until would do; both were silent about the value it carries, a set that must not grow and a stack the tree does not have, and both of those were found by asking, not by reading.

42. “Nothing checks a read of $x in a clause whose node type has a field x … and it is decidable from the description”

ROADMAP § 5’s words, written on 2026-10-10 after languages/units/ read a field where its pass had handed a table down. Scored on 2026-10-11, when the check was built, with the standup’s forecast for the make defect beside it, because both were a fix named in a sentence before anybody tried it.

Held: decidable. Every read the check needs is in the description, and it was written in an afternoon from functions check.c already had.

Failed, as a scope: “a clause”. Built as written, it warned twelve times in c.phx and never about a mistake: a Cast inside a declaration reads its own base field while the pass hands the declaration’s base down. Of the two kinds of clause the sentence covered, only one can mean the handed-down value at a node with the field. A rule for one type is written looking at that type’s fields; a default names no type. The check asks defaults only, and the repository is silent.

Held, with a condition nobody wrote: “a stamp of the sources’ checksum”. A stamp file is a file with a timestamp, and make 3.81 compares those to the second too, so a stamp written in the binary’s second would have been trusted exactly as the binary was. The checksum had to be compared when the Makefile is read, before make looks at any timestamp.

A fix named in one sentence is scored by what it is asked of. Both were right about the mechanism and wrong about where it applies: one asked every clause when only one kind can be wrong, and the other compared in the place that had already failed.