← board

A duplicate forward in rparser.inc breaks the FPC seed build

Found 2026-08-29 by frankA, gating an unrelated Track A fix. Not fixed here: rparser.inc is Track R's file and another agent may be mid-edit in it.

Symptom

rparser.inc(2786,10) Error: Function is already declared Public/Forward
  "RParseAggregateIntoNode(LongInt;LongInt):LongInt;"
compiler.pas(2133) Fatal: There were 1 errors compiling module, stopping
Error: /usr/bin/ppcx64 returned an error exitcode
gate: RED

Cause

Two identical forward declarations survive in the same file:

line added by
1498 e3043236b (20:26) feat(rust): enum values in expression position, and derived clone
2786 fcfe1cba1 (18:28) feat(rust): the engine's own idioms compile — chess.rs shapes end to end

The definition is at :2882. FPC allows one forward per routine; a second is an error even when identical. Deleting either forward should do it — keep the one that actually precedes the earliest call (:1506), which is :1498, and drop :2786.

Why it matters more than it looks

pxx does not care. It accepts the duplicate, so make compiler/pascal26 converges, the self-host fixedpoint is green, and testmgr --tier quick passes. The ONLY thing that notices is FPC — which is the seed. So the tree is in the state where everything an agent normally checks says fine, and the ability to bootstrap the compiler from source is gone. That is the one property the seed canary exists to protect.

It has been on origin/master since the two commits merged, and it fails on a clean checkout with no local changes.

Measured at HEAD fa238413e, 2026-08-29

$ fpc -Mobjfpc -O2 -Tlinux -Px86_64 -FU$D -FE$D -o$D/seed26 compiler/compiler.pas
rparser.inc(2786,10) Error: Function is already declared Public/Forward
compiler.pas(2133) Fatal: There were 1 errors compiling module, stopping
fpc exit=1

The worse half: the canary that exists to catch this now SKIPS it

This is the part to fix first, and it is Track A/T's, not R's.

gate.sh quick arms the seed canary only when git diff merge-base(origin/master, HEAD) -- compiler/ is non-empty — a deliberate choice, documented at tools/gate.sh:262-273, so the gate does not re-run a sibling's already-pushed compiler commit on every invocation. The consequence was not considered:

So the arming rule converts a repo-wide breakage into a silent one that mis-attributes on its next sighting. The rule is right about not re-running siblings' work; it is wrong to treat "already on origin" as "already proven". Cheapest correction: keep the arming rule, but ALSO arm when the previous canary result for the current origin/master sha is unknown or RED — i.e. remember the last seed-green sha, and skip only when origin/master has not moved past it. That preserves the cost argument (one seed build per origin/master advance, concurrent, ~11s) and closes the hole.

A gap this exposes in the fast check — for whoever owns forwardlint

tools/gate.sh quick runs two FPC-related steps, and the fast one was silent:

PASS  fpc seed compiles (forward decls)   (3s)     <- tools/forwardlint.py
FAIL  FPC seed canary (concurrent)                 <- the real ppcx64 build

forwardlint exists precisely so seed drift is caught in ~4s instead of minutes. It enumerates routines called from an include EARLIER than the one defining them — i.e. MISSING forwards. A DUPLICATE forward is the same class of defect (a forward-declaration mistake that only FPC rejects) and it does not look for it. Adding "the same routine forwarded twice in one file" is a few lines and would move this from a minutes-long failure to a seconds-long one. Track T or A, whoever holds tools/forwardlint.py.

Gate

tools/gate.sh quick green — specifically the FPC seed canary step — plus Rust tests still passing and the self-host fixedpoint unchanged.

Log