← board

gate.sh quick should smoke the PINNED compiler against lib/rtl

The gap

The per-fix loop builds and gates with the freshly self-hosted compiler. Track B builds with the pinned one. Nothing in the dev loop ever runs the pinned binary, so a change that makes the tree's lib/rtl require a compiler newer than the pin is invisible to every gate a dev track runs — while breaking Track B completely.

That is what happened: PXXVariantErrorHook was added to compiler/builtin/builtinheap.pas (compiled INTO the binary) and referenced from lib/rtl/sysutils.pas (ordinary source) in one commit, with no pin. Every $(PXX_STABLE) -Fulib/rtl <anything>.pas then failed with undefined variable (PXXVariantErrorHook) — including a three-line hello-world. It surfaced only as an ADVISORY demos#00 red.

Proposed check

# the pinned binary must still be able to build the tree's RTL
printf 'program s;\n{$mode objfpc}{$H+}\nuses SysUtils;\nbegin WriteLn(IntToStr(42)); end.\n' > $tmp/pinsmoke.pas
$(PXX_STABLE) -Fulib/rtl $tmp/pinsmoke.pas $tmp/pinsmoke && $tmp/pinsmoke

Sub-second, no compiler rebuild, and it fails loudly with the real error. It belongs in quick rather than a breadth tier precisely because the failure it catches is created by a dev-lane commit and invisible to the dev lane.

Design note — what it must NOT become

This is not "run Track B's gate in the dev loop". It is one file, chosen because SysUtils is what every Track B build pulls, and its whole job is to answer a single question the dev loop cannot otherwise ask: can the blessed binary still build the tree's RTL? Keep it to that; if it grows into a lib-test shard it will get deleted for costing ten minutes, which is how the dev loop is supposed to react.

Consider the same one-liner as a make pin PRE-condition, so a pin that would not fix such a break is caught before it takes the lock.

Gate

Track T's own breadth run green, and the new check verified both ways — passing at a pinned/RTL pair that agrees, failing with the real error message at f3acfdabf's tree.

Resolved 2026-08-26 (pxx-aa, Track T) — and it was a DUPLICATE

This and [[bug-t-gate-quick-cannot-see-a-broken-pinned-rtl]] are one defect filed twice, from two separate incidents four days apart, both at prio 65: PXXVariantErrorHook (2026-08-22, this ticket) and PXXNilRefHook (97b1812fe). Neither ticket cites the other. That the same seam produced two independent reports is itself the argument for the check — the class has an unknown population and was being rediscovered by accident each time.

The canary landed under the other ticket (1cc54252e). Two deltas from this ticket's spec were real and are now in:

1. It RUNS the program, not just compiles it. The other ticket argued compile-only ("running it is not the point"); this one wrote && $tmp/pinsmoke. This one is right, and they are two questions rather than one preference: the compile asks whether the frozen builtin still satisfies lib/rtl's references, the run asks whether the result works. A pinned RTL that compiles and then dies is exactly as broken for Track B, and nothing else in the dev loop asks. The two failures print different messages so triage stays sharp — a run failure is NOT the frozen-builtin seam.

2. make pin verifies the binary it just blessed. Suggested here as a PRE-condition; implemented as a POST-freeze one, because before the freeze the frozen set is still the old pin's and the answer is about a binary nobody is about to commit. It exits 1 rather than warning: the entire cost of this failure is that nobody looks, so a warning that scrolls past is the same as no check. It names the undo (make revert) because the pin has already moved by then.

This is the other end of the seam gate.sh guards. gate.sh catches the commit that breaks $(PXX_STABLE); this catches the pin that fails to fix it — taken while holding the repo-wide lock, which is the expensive moment to be wrong.

Verified failing, both ends

Not asserted — executed. In a scratch tree with one injected reference in lib/rtl/sysutils.pas to a builtin the frozen set does not define, the pin recipe's own fragment prints

PIN VERIFY FAILED — the binary just pinned cannot build lib/rtl:
  pascal26:5306: error: undefined variable (PXXCanaryProofHook)
  The pin HAS already moved. Undo with: make revert

and exits 1; the gate canary fails the same way through gate.sh's step wrapper. Green against the real tree in both places.

tools/gate_pinned_rtl_canary_devtest.py guards both, including that the canary still RUNS what it built (the half a later cleanup would drop as redundant) and that the pin check still exit 1s rather than warning.

Log