Progress board
Generated by tools/progress.sh board-md — regenerate after any board
change; tools/progress.sh check fails if this file is stale. History
lives in git, not in a timestamp.
urgent (0)
none
working (34)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| bug-a-nilpy-on-cross-targets-four-remaining-walls | A | 40 | bug | PER TARGET, BY MECHANISM (re-measured 2026-09-19, frankS). UNDER ESP-IDF (--platform=esp) BOTH ESP ISAs WORK, UNDER QEMU ONLY -- no chip has run either: one class/list/loop/print program builds, links with idf.py and prints CPython's output byte for byte with one boot, on the ESP32-C3 (riscv32, examples/esp32/nilpy-c3) and the ESP32-S3 (windowed xtensa, examples/esp32/nilpy-s3), ./build.sh qemu-assert. Needs a compiler newer than pin v412. What had to change is ISA-neutral runtime (the NilPy arena reserved in BSS on IDF; softfloat before builtinheap; ParamCount/ParamStr answer 0 on ESP; Write reaches libc's stdout STREAM; the program end deletes the FreeRTOS task instead of busy-parking) plus per-ISA codegen: riscv32's IR_SYSCALL answers -ENOSYS on ESP; xtensa's exception frames live in fixed frame slots instead of being pushed under the spill stack, its landing pads use a long jump past 128 KiB, a call passing more argument words than the windowed outgoing area holds carves the rest below sp with MOVSP, and every call shape marshals an argument by its class (the virtual/indirect/ctor sites knew only records). The S3 image still needs --xtensa-long-calls (feature-a-xtensa-should-not-need-a-flag-to-build-a-large-image) and neither ISA can use --dce on IDF (bug-a-dce-drops-a-called-body-on-the-riscv32-idf-profile), so the image is ~3 MB of flash. BARE metal (--esp-profile=bare, both ISAs) is walled by DESIGN, not a bug: builtin is unsupported on a bare boot and NilPy needs it (feature-bare-esp-supports-uses-builtin). Other targets UNCHANGED since 2026-08-31 and NOT re-measured: arm32 works; i386 symbol kind not supported yet (load); aarch64 has no stack-argument passing for 5 of 6 call kinds; hosted riscv32/xtensa Linux has no mmap arm for the arena; wasm32 undefined variable (SYS_openat). |
— |
| bug-a-pascal-nilpy-rust-and-zig-over-align-an-8-byte-member-on-i386 | A | 45→75 | bug | PASCAL DONE AND PROVEN; the THREE REMAINING HALVES ARE NOT ONE POPULATION and 2026-09-06 measured which is which. RUST AND ZIG ARE LATENT, NOT LIVE: both refuse every non-x86-64 target at the top of the parse (rparser.inc:5774, zparser.inc:1983) and TypeAlign only differs from TypeFieldAlign when TargetArch = TARGET_I386, so neither frontend can reach the defect today -- it is a trap for whoever lifts the target restriction, not a wrong answer anyone can obtain. NILPY IS REACHABLE AND MEASURED WRONG: --target=i386, one compiler, the aggregate {1-byte b; double y} comes out C b@0 y@4 size 12, Pascal b@0 y@4 size 12, NilPy b@8 y@16 size 24 -- seven bytes of padding where falign=4 asks for three. THE OBSTACLE THIS TICKET WAS BUILT ON IS NOW FALSE: PXXDBG=a.reclayout (landed 2026-09-06) prints every aggregate the compile laid out straight out of the shared UClass/UFld tables, so all five frontends have a layout observable and the comparison is one compiler disagreeing with itself -- the same oracle that dissolved the Pascal fork -- with no export spelling and no rustc/zig needed. WHAT IS STILL HONESTLY OPEN FOR NILPY IS THE CLAIM, NOT THE READING: NilPy has no cdecl export and no ctypes (ProcCdecl is set only from cparser.inc and pasparser_*; pyparser.inc mentions neither), so nothing outside pxx reads a NilPy instance and the over-alignment costs SIZE today, not a wrong value. PASCAL DETAIL BELOW UNCHANGED. The four fAlign := TypeAlign(fTk) record-field sites in pasparser_decl.inc now call TypeFieldAlign, and a new mixed-link oracle (test-record-abi-mixed-link) judges the layout against gcc across a real link on x86_64 and i386, four shapes plus a value round trip through a cvar record global. The Track U fork this ticket flagged -- whether a Pascal record must match the C ABI -- dissolved on the first measurement: pxx's C frontend answered 12/4 and pxx's PASCAL frontend 16/8 for the same fields, same target, same compiler. It was not an FPC question, it was one compiler disagreeing with itself. N/R/Z have no export spelling and so no mixed link, and rustc/zig i686 are not on this box. |
— |
| bug-n-os-environ-and-os-sep-are-not-values | N | 60→90 | bug | os.environ is not a first-class value: 'X' in os.environ is error: undefined variable (os), while os.environ.get('X') compiles. RE-MEASURED 2026-09-10 at compiler 98b6545b4652 and THE SLUG IS HALF STALE: os.sep WORKS -- it prints / -- and so does os.linesep. PyIsStdlibMemberValue gained both at 996bcf5a8 on 2026-08-29 -- the DAY AFTER this ticket was filed -- and this summary was never updated, so a reader picking this up would spend the first measurement discovering that half of it is done. What is left is environ specifically, which is not a constant string but a MAPPING, so it needs a value the in operator and .get/[] can both reach -- a different job from adding a name to that gate's list. SIBLING BUT NOT THE SAME GATE -- re-measured 2026-09-10 while taking both as a group: PyIsStdlibMemberValue is consulted for sys and os and nothing else, and was never in math.sin's path, so that ticket's fix (FindProcInUnit and the qualified-member value door) does nothing here. What DID land is the diagnostic: f = os.getcwd said undefined variable (os) and now names the shim and the workaround. os.environ as a mapping and the remaining os constants are untouched and are what is left; the third door frankuser grouped with them, staticmethod, turned out NOT to share it -- a builtin name is a separate mechanism and was fixed separately on 2026-09-10. Original measured cost stands: it is the single largest wall in the reportlab probe, one 7-line file blocking 30 of 159. |
— |
| bug-p-a-class-nested-type-as-a-specialization-argument-resolves-at-unit-scope | P | 60→65 | bug | A type named as a SPECIALIZATION ARGUMENT is resolved where the template body materialises, not where the source wrote it. THREE DOORS -- three ways to be in neither table NestedSpecArg consults. Door A (a non-generic class, its own or a non-generic ancestor's nested type) is fixed at 3a89c6184; door C (inherited from a GENERIC ancestor, the rtl-generics shape) at 61a9463be; door B (the template's own body) always worked. The silent arm answers 300 where it answered 44. Every ladder row now passes except v7, which is bug-p-a-qualified-type-name-cannot-be-a-generic-argument and unchanged. Two regression tests, both byte-matching fpc 3.2.2, both with a clean negative control on a pre-fix binary. STILL OPEN, and it is one question: the Generics.Collections driver compiles end to end (11 errors, 6m59s) where it used to abort, and of its 8 TEnumerator mints seven now resolve while exactly ONE is bare alias=TEnumerator$PT -- which site produces it is unidentified, and the one experiment that looked like it would answer that made the corpus worse and settled nothing. SEPARATE, filed, and NOT a blocker: bug-p-a-hoisted-nested-type-name-leaks-between-two-specializations-of-one-template, a PRE-EXISTING leak verified on a binary without either fix, which is why both tests here instantiate each template once. |
— |
| bug-p-a-generic-cannot-hold-a-parameter-swapped-specialization-of-itself | P | 20 | bug | TPair<K, V> = class FSwap: TPair<V, K>; end; -- a specialization of the same template with its parameters SWAPPED -- is refused with circular generic specialization: TPair$LongInt$ShortInt requires TPair$ShortInt$LongInt, which requires TPair$LongInt$ShortInt back. True as stated: pxx specializes by emitting each declaration before its users, and these two need each other. Both surfaces agree since the self-other fix. FPC 3.2.2 also refuses it (differently); real Delphi accepts it. Architectural, not a mis-parse -- the diagnostic is honest and the program does not compile wrong. |
— |
| bug-p-a-specializations-concrete-argument-is-keyed-by-its-spelling-so-two-scopes-types-collide | P | 50 | bug | A specialization's concrete argument is keyed by its SPELLING, so two different types of one name collapse into a single specialization: TRec declared in an outer routine and a DIFFERENT TRec in a nested one both key TBox<TRec>, and the nested SizeOf answers the outer record's 3 where fpc answers 1. The bare name resolves correctly a line earlier — the compiler knows which type is meant and the specialization does not ask. THREE ARMS, one cause. Class/record templates: FIXED 2026-09-08. The ALIAS MIRROR (bug-p-a-nested-specialization-is-named-by-its-alias-...), the same flaw in the opposite direction — two aliases of ONE specialization over-minting where this under-mints: RESOLVED 2026-09-08, and NOT by the canonical key this ticket predicted would close it. Generic ROUTINES: STILL OPEN and blocked on PASS ORDER, not on keying — its keying half is a different mechanism (SpecFuncAlreadyEmitted, name plus value-parameter count), and fixing its visibility half ALONE turns a compile error into a silent wrong value by handing Test2 the body compiled for Test1's TTest. Do not fix the remaining arm by making the comparison stricter in isolation, and do not carry this ticket's canonical-key prediction into it unre-derived: the alias mirror closed without touching the mint at all. Corpus row: tgenfunc10.pp, still unknown type: TTest. |
— |
| bug-p-an-enum-or-array-type-cannot-be-named-as-an-operator-operand | P | 30 | bug | THE ARRAY HALF IS DONE; the ENUM half is open and is a type-system question, not a lookup. A NAMED array type is now a legal operand type -- operator and (a, b: TArr) over array of Char and operator + (a, b: TNums) over array of LongInt both compile and fire, fpc-identical on five targets. The ticket said the array half was 'a lookup' and that was MEASURED WRONG: an array's TypeKind IS its element kind, so registering TArr under tyChar makes it the SAME (kind, recId) row as Char -- with both declared, c and d on two Chars ran the ARRAY body and segfaulted inside Length. It took a REC_ARRAY_OPERAND key and FOUR consumers of the same fact: the operand NAME door, the use-site KEY (OperandRecOfNode), the declaration-time PREDEFINED check, and the use-site overload GUARD (OperandPairMayOverload, which now also has one spelling instead of two copies). toperator78's wall moved from line 9 to line 19 and the row stays skipped; tarray18 is NOT covered -- its operand is an anonymous array of LongInt in an open-array PARAMETER, a different shape. The enum half still needs tyEnum: pxx has none, so operator * (a, b: TEnum) would be refused as predefined where fpc accepts, and the column cannot be exactly right without minting a kind. |
— |
| bug-p-an-operator-enumerator-cannot-be-declared-for-an-array-type | P | 30 | bug | operator enumerator(a: TDyn): TEnum and the static-array form are refused at the DECLARATION with operator overloading: <T> is not a supported operand type. fpc 3.2.2 accepts both and runs them in preference to its own built-in array iteration. RE-SCOPED 2026-09-08, twice over, both corrections downward. (1) It is NOT a missing entry in an accepted set: the Ovrl table carries three columns — OpKind, TypeKind, RecId — an array type has no RecId, so an array operand can only key on its ELEMENT kind and collapses onto the row for that scalar. That is the identical collapse tforin15 is skipped for, whose reason ends do not half-plumb it, and the use site cannot supply an array identity either: no AST node carries an ArrType row, so the expression spelling could not be keyed even if the declaration were. Same channel as bug-p-a-distinct-type-declaration-is-parsed-but-is-not-distinct, extended to operators. (2) It does NOT block the for-in precedence work, and this ticket said it did. Those two families have exactly ONE enumerator candidate in pxx, so a precedence RULE is vacuous there rather than untested — what is unmeasurable is the array arm, not the rule. |
bug-p-a-distinct-type-declaration-is-parsed-but-is-not-distinct |
| bug-p-nilpy-diagnostics-exist-on-both-arms-of-the-parsefactorcore-carve-out | P | 55 | bug | PARTIALLY RESOLVED 2026-09-06 (2626683d6): the 11 top-level else if NilPyUserCode and (name = ...) arms in ParseFactorCore are DELETED -- exec eval input format open float map filter next bool str, 608 lines, and the compiler code segment drops 254KB. Zero top-level NilPyUserCode arms remain in the function. NOT DONE: 17 Nil Python: diagnostics are still duplicated between pasparser_expr.inc and pyparser.inc (was 28), carried by the NESTED if NilPyUserCode uses the deletion deliberately left -- name, file, pystr_of, len, int, round, divmod -- plus sites outside ParseFactorCore. Those are a different shape and are NOT covered by the subsumption proof: at least one of them, TPyList.extend in ParseExpr, is MEASURED LIVE, so this residue must not be swept on the pattern. What licensed the deletion was guard subsumption, not the corpus: below the dispatch PyExprMode is false by construction, so NilPyUserCode reduces to isNilPy and (CurrentUnitIdx < 0), and isNilPy is set from the root file extension -- unsatisfiable for a .npy whose main program is Python. The differential is now trustworthy where the old 0/229 was not: 242 programs that USE these builtins, identical=242 DIFFER=0, against a positive control (live str arm disabled) scoring DIFFER=19 of 19 through the same normalisation. |
— |
| bug-p-the-two-halves-of-an-overload-report-spell-an-array-argument-differently | P | 50 | bug | FIXED 2026-09-07, both halves, and the free half was a different bug from the method half. The argument side of no overload of X matches printed a raw TTypeKind while the candidate side printed an IsArray-aware spelling, so a CORRECT array argument read as a mismatch. TWO causes: the printers had no array-aware argument spelling (one new function, ArgSpellingForReport, reading the MatchArgArray channels that already existed — the tickets stated signature-change obstacle was wrong), and on the FREE path the failed variadic-bracket-elision retry overwrote OverloadReport with a report about the argument list IT invented. Both halves now print array of record; two negative fixtures in test-core, controlled against a compiler with the fix stashed out. |
— |
| bug-t-pin-verify-builds-with-the-previous-pin-not-the-one-it-names | T | 80 | bug | FIXED 2026-09-06, fixture-tested, ONE STEP UNCONFIRMED and NOT closed by 1f8c2b3a2: seven is measured STALE (code_fp 7327e547732c = 17854b85b, 09-05), and the pin row 1f8c2b3a2 filed tonight is the OLD 10-key shape and is a fresh instance of the defect -- it verified v406 (VERSION at tree 04559b9d6) under the name v407 (pin commit 51901941e). Beware: ordinary run rows have carried 17 keys and a code_fp since 17854b85b, so a 17-key row is NOT evidence the fix is live; only the PIN row discriminates. verify_pin checked out the pinned TREE, and a pin commit is always a DESCENDANT of the tree it pins, so stable_linux_amd64 came back holding v(N-1): every $(PXX_STABLE) job built with the PREVIOUS pin while the verdict was filed under vN. MEASURED, not reasoned -- VERSION at the pinned tree is exactly N-1 for all nine pins v399..v407. THE FIX (option 1): restore stable_linux_amd64 from the pin COMMIT after checking out the tree, then clean that dirt with git checkout HEAD -- stable_linux_amd64 before clone_head_back, keeping clone_head_back strict. Option 2 was argued down: re-keying the row to the pin commit breaks trackt.read_pin_log's join on the tree. BACKSTOP: report['pin'] is compared against the version being published and a mismatch publishes NOTHING, so a failed restore degrades to a gap rather than a wrong record. 24 guard rows in twatch_pin_identity_devtest.py on a fixture repo shaped like a real pin, including the defect reproduced and a wedge control. THE WEDGE IS CONDITIONAL, found by that control failing: git refuses the branch checkout only when the tip's artefacts DIFFER from the restored ones, so verifying the current pin usually would not have wedged and verifying an overtaken one would. |
— |
| feature-a-a-stackful-coroutine-is-four-targets-only-so-examples-net-httpdemo-cannot-cross | A | 45→80 | feature | riscv32 is DONE -- examples/net/httpdemo builds and runs there with output byte-identical to the x86-64 oracle, and i386/arm32/aarch64 are unchanged. XTENSA REMAINS, and it is the hard half: under the windowed ABI the callee-saved state lives in a rotating register window rather than on the stack the way CoSwitch assumes, so a stack switch has to spill the window first. riscv32 took FOUR pieces, not the three this ticket predicted -- the CoSwitch stub, the IR_COSWITCH lowering, scheduler.pas's initial frame and epoll syscall numbers, AND atomic codegen, which riscv32 refused in user mode because its only primitive was the ESP arm's machine-mode interrupt mask. | — |
| feature-a-dynamic-array-of-frozen-strings | A | 45 | feature | PREMISE REFUTED 2026-09-03: the stride IS known and it is 8388616 bytes -- STRING_CAP + 8, taken from the ARRAY VARIABLE's storage class, which is a category error for a dynamic array whose elements live on the heap. Only x86-64 refuses; i386/aarch64/arm32/riscv32 ACCEPT it, match FPC 3.2.2 on three elements and SIGSEGV at 1000, all reproducible on pin v401 -- so x86-64's refusal is the only honest behaviour of the six and this is not a missing implementation. Now blocked on [[decide-a-what-is-a-plain-frozen-strings-capacity-255-or-eight-megabytes]], because the element stride IS that number: a plain frozen string is allocated at 8388616 (global) or 264 (local/field) and clamped at 255 in every one of them, by assignment as well as by concat. ORIGINAL TEXT, kept because it is what the ticket was filed on: In the FROZEN-string model (-uPXX_MANAGED_STRING, the self-host build), array of string is refused from SetLength up: the element is an inline fixed-capacity buffer and no path knows its stride. Delete/Insert refuse it downstream of that, which is why they carry a frozen-string exclusion. |
decide-a-what-is-a-plain-frozen-strings-capacity-255-or-eight-megabytes |
| feature-a-object-output-for-arm32-and-aarch64 | A | 45→80 | feature | BOTH HALVES LANDED AND VERIFIED, 2026-09-22 (aarch64, then arm32). Each shares its ELF-class writer rather than cloning it -- aarch64 in writeELFRelX64General, arm32 in writeELFRel386General -- with byte-identity of four saved x86-64/i386 objects as the control for both lifts. THE PSABI WAS THE WRONG PLACE TO DESIGN FROM ON BOTH: these backends materialise addresses from an INLINE LITERAL POOL, so three of four sites are DATA WORDS in .text and only the external call is an instruction field, where one site takes TWO relocations. THE MECHANISM WORTH REMEMBERING IS THE SHARED READING: the aarch64 MOVW type numbers were wrong in the writer AND in its own harness, identically, so every comparison between them was green -- only clang's object could see it. Every arm32 number was therefore OBSERVED, never read, and R_ARM_ABS32 = 2 is the one an analogy gets wrong three times out of three (1 is R_ARM_PC24, which does not refuse). arm32 gives each GOT slot its OWN LOCAL SYMBOL with addend 0, because SHT_REL keeps the addend in a SIGNED 16-BIT split immediate and .data is 607044 bytes here -- a writer built the aarch64 way passes every test in this repo and fails on the first real program, since every probe is small enough to fit. Verified: aarch64 AGREE on 772256 bytes / 1355 relocations, arm32 AGREE on 835712 / 1354, 4 of 4 controls each, plus shape, coherence and split-addend oracles against clang. FOUND HERE AND FIXED THE SAME DAY: an object carried a DUPLICATE .rel.data entry, harmless on RELA and a DOUBLED BASE on SHT_REL -- measured against GNU ld on i386, 0x100a00b1 where 0x8054f51 was meant. Collapsed in the shared method-fixup compaction pass, guarded by a pair invariant in structural_check whose positive control is the three pre-fix objects. | — |
| feature-a-record-rtti-descriptors-for-initializearray-and-finalizearray | A | 40→75 | feature | MEASURED 2026-09-06 at 88a0b3d93835. InitializeArray(P, TypeInfo(TFoo), N) and its Finalize twin do not exist -- undefined variable (InitializeArray), and nothing in compiler/ or lib/rtl mentions either name. They are the RTTI-DRIVEN form of a management operator: given a raw pointer, a TypeInfo and a count, run the record's Initialize/Finalize over N elements. FPC's own RTL uses them wherever the element count is not known at compile time, which is why the SYNTACTIC form ([[feature-pascal-management-operators-nested-and-array]]) cannot subsume them -- that one desugars an lvalue it can see, this one is handed a pointer and a descriptor. THREE CORPUS ROWS ASK FOR IT, one cause: fpc testsuite tmoperator2 (line 100), tmoperator3 (line 82), tmoperator9 (line 48), all three stopping on the same undefined name. PREDICTED BY THE TICKET IT IS NOT PART OF: nested-and-array's Sketch says the dynamic-array and class-field cases are 'genuinely the RTTI shape FPC uses ... the point at which a Track A ticket for record RTTI descriptors is the right answer'. This is that ticket, filed with the demand attached rather than as a shape. TWO HALVES AND ONLY THE SECOND IS THE HARD ONE: the System helpers are a loop over a descriptor, but TypeInfo(TRec) must first CARRY the management-operator entry points for a record, which is a Track A RTTI-emission question, not a parser one. NOTHING PAST THE FAILING LINE IS VERIFIED in any of the three rows -- each stops at its first InitializeArray, so what those files assert afterwards is unmeasured and must not be quoted as passing or failing. |
— |
| feature-a-there-is-no-read-only-load-segment-so-nothing-can-be-flash-resident | A | 70 | feature | FIRST CUT LANDED 2026-09-18 (frankH): x86-64 executables (aarch64, i386 and arm32 too since 2026-09-19 -- every hosted target) load the string-literal pool through a third PT_LOAD with flags R (static and dynamic links, -g included; --no-ro-data turns it off). The compiler's own image: 555 KB of its 574 KB data is now read-only. Mechanism: ranges of Data[] are marked at emission (RoRangeAdd), the writer permutes them to the front and every data address resolves through DataRemap, so no emitter changed. The segment found a real writer on day one -- x86-64's inlined SetLength released the old block with no MSTR_STATIC_RC guard, decrementing a literal's count -- fixed in the same change. ESP-IDF LANDED 2026-09-18: both ELF32 object writers emit .rodata (flags A) + .rela.rodata, which IDF places in flash -- test_emit_obj.pas on xtensa: SRAM .data 6304 -> 2624 bytes; a literal an iram; routine references directly stays in .data (iram code runs with the flash cache off). RTTI/VMT LANDED 2026-09-19: class RTTI headers and Pascal VMTs are read-only BY DEFAULT in hosted executables (--no-ro-rtti turns it off), after a never-written measurement across test-core, lib-test, the pcl GUI suite and the i386/aarch64/arm32 tiers. ESP objects keep them in .data, because an ISR reads a VMT through an instance with the flash cache off. REMAINING: NilPy VMTs, prop/method arrays, IMTs, dispatch tables, float constants, each after its own never-written measurement. Typed constants stay writable ({$J+}). The bare ESP profile gains nothing -- a fact about OUR profile (one RWX IRAM region, qemu's shape), not the chip. | — |
| feature-a-xtensa-should-not-need-a-flag-to-build-a-large-image | A+S | 35 | feature | PRIO HELD AT 35 by the owner 2026-09-05, when the population was ONE program; it is TWO since 2026-09-19 and the second is an application, so the premise of the hold has moved (the prio has not -- that is his). MECHANISM: a forward call is sized before its callee's body exists, so a call site whose callee lands past CALL8/CALL0's +-512 KiB reach has no room for a wider form, and the build refuses; --xtensa-long-calls reserves the wide form everywhere and builds. Ordinary C no longer meets it: f49c0e11f reserved the wide form unconditionally for FiniRunnerProc, and #include <stdio.h> and test/c_crtl_syscall_guarded_bodies.c link and RUN with no flag. It is met by any image big enough that ordinary-proc-to-ordinary-proc calls span more than 512 KiB: the compiler itself (pascal26 --target=xtensa compiler/compiler.pas, a call 23 MB out) and ANY NilPy program, whose runtime is ~2.9 MB of code without DCE -- examples/esp32/nilpy-s3 (a NilPy app on the ESP32-S3 under IDF) passes the flag in its build.sh for that reason. DCE shrinks the second case but does not remove the mechanism, and --dce is not usable on the ESP IDF profile yet. Untried candidate: a veneer slot reserved per CALLING BODY rather than per call site, which the banked negative does not rule out the way it rules out an unreserved veneer. |
— |
| feature-b-buffered-text-io-and-settextbuf | B | 55 | feature | Make lib/rtl/textfile.pas's read buffer caller-supplyable and add write buffering, so SetTextBuf can exist with FPC's exact semantics. READ SIDE DONE at 8dacaaa15: the inline 4096-byte array became BufPtr/BufSize over an inline DefBuf and SetTextBuf exists, byte-identical to FPC 3.2.2's own run. Write side buffers under C99 7.19.3p7's policy, NOT FPC's — measured: FPC's destroys stdout/stderr ordering whenever stdout is not a tty. DO NOT LAND THE WRITE SIDE ALONE: this is one half of an interlock with feature-c-crtl-stdio-buffering-and-setvbuf, and the two share a flush registry. Ordering between Pascal WriteLn and C printf is correct TODAY only because both sides are unbuffered; buffering either side by itself reorders output inside a single program that mixes them, which is the case pxx exists to support. crtl's setvbuf is also a stub that ignores its arguments and returns SUCCESS -- worse in C than a missing SetTextBuf is in Pascal, because C callers check the return, so it turns a missing feature into a wrong answer. WHAT IS LEFT IS THE WRITE SIDE, and it is bigger than this ticket assumed: MEASURED 2026-09-04, plain writeln does NOT go through textfile.pas on any target -- every backend lowers IR_WRITE/IR_WRITELN to its own emitted write, so buffering Output here buys nothing for the common case. See the 2026-09-04 finding in the body before planning it. |
— |
| feature-c-crtl-stdio-buffering-and-setvbuf | C | 55 | feature | lib/crtl/src/stdio.c is entirely unbuffered — fputc is one write() syscall per character — and setvbuf at :1051 is a stub that ignores its arguments and returns SUCCESS, which is the dishonest-stub shape the SetTextBuf ruling exists to reject, and worse here because C callers check the return. Add FILE write buffering under C99 7.19.3p7's policy, make setvbuf real, and share a flush registry with lib/rtl so mixed WriteLn/printf output keeps its order. | — |
| feature-n-a-non-allocating-restricted-thunk-for-an-isr | N | 60 | feature | TWO PREMISES MEASURED AND BOTH FALSE; WHAT SURVIVES IS THE MISSING ENFORCEMENT. (1) THE THUNK DOES NOT ALLOCATE for the scalar case -- 100x the iterations gives +2 allocations (N=200 allocs=5, N=20000 allocs=7) against a positive control that scales exactly 100x (727 -> 72269), and even a (const AnsiString): AnsiString slot crosses allocation-free. THE ALLOCATION IS IN THE DEF BODY: def grow(s): return s + 'x' allocates ~1 per call. (2) THE JUSTIFICATION IS CONTRADICTED BY THE PLATFORM. This summary used to say an ISR that allocates is a latent crash that fires when the interrupt preempts code HOLDING THE HEAP LOCK. There is no such lock on either ESP profile. IDF: PXXAlloc is backed by calloc/free into heap_caps and multi_heap_platform.h:18 picks portmux spinlocks over RTOS mutexes BECAUSE malloc/free can happen in an ISR -- safe by deliberate design, the cost being latency and determinism. BARE: riscv32/xtensa are in neither the softlock nor hardlock target list, so there is no lock at all, and --threadsafe is REFUSED there rather than silently ignored; the free list is safe only BY ERGONOMIC UNREACHABILITY, which is WEAKER than the flat 'unreachable' this summary said until 2026-09-21: frankb-8e reports that a hand-written assembler routine CAN be installed as a raw vector and always could -- test/test_esp_bare_csr.pas writes mtvec ($305), reads it back and takes two ecalls through a hand-written handler that steps mepc and mrets, and no refusal touches ordinary code with an ordinary address. So the hazard is reachable TODAY by a determined programmer writing raw asm and not by anyone writing Pascal, and a compile-time refusal on a NilPy def cannot protect the asm path and is not meant to -- the acceptance pair must NOT be read as 'no interrupt can reach the allocator'. SO THE MECHANISM, STATED SO IT DOES NOT DECAY WHEN AN INSTANCE IS FIXED: nothing refuses a def whose BODY can reach the allocator, and the thunk cannot fix that because the thunk is not where the allocation is. THE TRIGGER HAS FIRED, VERIFIED HERE 2026-09-22 AGAINST MY OWN BINARY (1e5dd067455e) AND NOT ON REPORT. frankb-8e reports (8858d57ae, 2026-09-21) that it built the ISR stack and narrowed the ir.inc refusal onto it in one commit, so h := @MyIsr is PERMITTED on bare riscv32 and a handler is entered by hardware; it also reports the other two arms still REFUSED (xtensa bare, riscv32 IDF -- and the IDF arm must keep refusing, since that is where PXXAlloc is calloc into heap_caps and where the port owns mscratch). MEASURED HERE RATHER THAN TAKEN ON REPORT, because 8e's claim about this trigger moved TWICE in one evening in OPPOSITE directions and it said so itself. Four arms, verdict keyed on the REFUSAL TEXT and not on rc: riscv32+bare PERMITTED (rc=0); riscv32 default-profile, xtensa+bare and xtensa default-profile all REFUSED with cannot take the address of MyIsr. CONTROL: the same source under pin v416 (fddc21e7e661), which predates 8e's work, is REFUSED on riscv32+bare -- so the check exists, the probe reaches it, and PERMITTED at HEAD is a real change rather than a probe that missed. NOTE ON THE FIRST ATTEMPT, because it would have confirmed 8e for the wrong reason: --esp-profile=idf is NOT AN OPTION (IDF is the default, only --esp-profile=bare exists), so two of the four rows first came back rc=1 from an unknown-option error wearing the shape of a refusal. Key such a table on the diagnostic text. THE POPULATION THAT OPENED IS BARE RISCV32 SPECIFICALLY, and the default-profile arm MUST keep refusing -- that is where PXXAlloc is calloc into heap_caps, where the port owns mscratch, and where interrupt; is the wrong keyword for esp_intr_alloc anyway. THE HISTORY, because it is the reason this line is worded this way: the trigger first read 'when the CSR/vector-install enabler lands' -- that landed (819a5e275) and bare was STILL closed, because taking the address of an interrupt; routine is refused in ir.inc independently of it, a SECOND interlock neither of us knew was doing that job. The real trigger, re-cited to a refusal in the tree rather than to a capability, is WHEN THE ir.inc @interrupt-PROC REFUSAL IS NARROWED, whose own stated retirement condition is a dedicated ISR stack for interrupt; bodies on bare; at that point note that the remedy a seat will reach for is WORSE than the hazard, and state it as a MECHANISM because the line number decayed within a day: the remedy is turning on --threadsafe, and PXXHeapSpin (builtinheap.pas, grep the symbol -- TWO acquire sites, both inside {$ifdef PXX_THREADSAFE}, so on bare today there is no lock to be stale about) is a bare xchg spin with no interrupt masking, so a task holding it and an interrupt contending for it on one core cannot make progress. ACCEPTANCE is a measured pair (must-reject grow, must-accept two), not the unsatisfiable 'reject the existing thunk once established that it allocates'. x86-64 only so far; the on-target context precondition is assertable via xPortInIsrContext ON IDF ONLY -- it is an external FreeRTOS symbol, so it does not exist on bare, and frankb-8e reports that even where it links, a RAW VECTOR entry never reaches rtos_int_enter and therefore answers 0 while genuinely in an ISR, so this instrument is silent about exactly the path the CSR enabler creates -- and where it does apply it MUST be asserted as <> 0 never = 1 (riscv returns the raw nesting count, xtensa a normalised boolean). (3) THE FRONTEND ARM IS PRICED AND THE COST IS NILPY'S ARBITRARY-PRECISION INTS, NOT THE VARIANT CONVENTION -- there is no Variant in def f(a): return a + 1 at all: the PARAMETER is already tyInt64 and the BINOP NODE is tyPromoInt64, one predicate (PyIntGrowsOp, two binop typing arms in pasparser_expr.inc) decides it, implementing decide-nilpy-int-promotion-default option 1. Twelve probes, one def per FILE: a, &, |, ^ and comparisons are CLEAN (0 bodies); + - * << >> reach PXXAlloc via the promo runtime; // % reach it via pyfloordiv_i/pyfloormod_i, whose ZeroDivisionError arm constructs an object. SO THE NON-ALLOCATING SUBSET IS NOT EMPTY AND IS NOT "NO ARITHMETIC" -- it is bitwise-and-comparison, and IT COVERS 0 OF 5: every routine installed as an interrupt or timer callback in examples/esp32//main/.pas is x := x + 1, including the sole iram; ISR-dispatch body whose own comment calls an increment the least work that can answer the question, and += lowers identically. THE FORK IS THEREFORE NECESSARY AND IS WRITTEN INTO THE BODY UNSENT: "do we want a NilPy function callable from an interrupt to compute with Python's integers, or with the machine's?" -- machine's = arm A, cheap to build and A SECOND INTEGER SEMANTICS (n+1 wraps, re-opening on a restricted scope the bug that decide ticket closed, and diverging from CPython in the direction nilpy-semantics-divergences.md does not permit); Python's = arm B, no frontend change, guard becomes a promise, and its precondition (PXXPromoFromInt spills to the heap outside +-2^31 when SizeOf(NativeInt) < 8) is violated on every ISR target and observable on none of the x86-64 machines the dev loop, gate.sh quick and the pin run on. SPLIT OUT OF feature-n-a-nilpy-def-has-no-native-abi-entry-point-to-hand-to-a-c-callback 2026-09-20. Parent sits at 85 on a secondhand relay that the owner called ESP interrupts a must-have; this half is 60 because nothing downstream is blocked on it today. |
— |
| feature-opt-heap-per-thread-cache | A+O | 48 | feature | Heap allocator serializes under threads — parallel alloc is 3x SLOWER than serial | — |
| feature-opt-nilpy-container-subscript-is-15-19x-slower-than-cpython | O | 55 | feature | Container subscript is NilPy's worst primitive against CPython. RE-MEASURED 2026-08-31 on a quiet box: b[2] is now 117 ns vs 11 (10.6x, was 234 vs 12 = 19.2x) and d['k'] 262 vs 25 (10.6x, was 16.5x) -- the absolute cost roughly HALVED, and the -O3 reserve this ticket recorded as 30-40% is now 3-7% because the static-literal pass promoted to -O2 exactly as predicted. All FOUR previously-named drivers are now resolved, so a ~10x gap remains with no cause. New suspect, named categorically and then CORRECTED the same night: a subscript costs 11 out-of-line calls, 8 into retain/release/release-and-clear -- three genuinely DIFFERENT operations (confirmed by which refcount helpers each calls), not duplicates, so do not merge them. What is real is that each re-derives the value's type tag with six compares, so the tag is classified ~8x per subscript for a value whose type never changes. Benchmark committed as bench/nilpy_primitives.npy. Next step is confirm-then-decide, not a fix. | — |
| feature-pascal-corpus-expansion | P | 75 | feature | RUNG 6 IS COMPLETE as of 2026-09-05: generics.collections compiles end to end (code=720664B data=163024B bss=127228B procs=1902, 4m33s), and so does generics.defaults (procs=1780, 16s). READ THE 2026-09-05 BLOCKQUOTE AT THE TOP OF LIVE STATUS BEFORE ANY OTHER FIGURE IN THIS FILE -- every other wall table here is a dated snapshot and they disagree by design. THE LAST WALL ON 6b WAS NOT IN THE FRONTEND, IT WAS THE PIN: for-in: enumerator has no readable Current fell to ONE LINE in lib/rtl/classes.pas (property Current: T read GetCurrent; on IEnumerator<T>), deliberately omitted since 2026-08-30 because Track B builds lib/rtl with $(PXX_STABLE) and the pin rejected a property in an interface; the parser fix sat in done/ doing nothing for this corpus until pin v404 (8844c8c42) carried it. ATTRIBUTED BY ABLATION, not by plausibility -- same binary, same source, that line removed reproduces the exact wall at :1481, rc=1. THE CLASS TO CARRY: "fixed at HEAD, inert until pinned" -- any compiler fix a $(PXX_STABLE) consumer needs is closed while still unusable there, the ticket folder gives the wrong answer and the pin gives the right one (devdocs/dev/track-b-workarounds.md names this state; THIS instance was missing from that registry because the workaround was an OMISSION and a missing declaration leaves no code to spot). STANDING LESSON FOR THIS FILE: corroboration speaks to the READING and says nothing about the AGE -- rung 6a was recorded green by two independent sessions with byte-identical figures on 2026-08-30, b613b5fcf broke it the next day, nobody re-ran it, and 6b then appeared to stop inside 6a's file, so the ladder had moved BACKWARDS while reading as a floor. Re-run the rung before you trust any row here. RUNG 7 (fcl-passrc, 60k LOC) IS IN PROGRESS as of 2026-09-06 with its TWO LARGEST UNITS DONE: pscanner.pp (5333) and pastree.pp (5947) both COMPILE, LINK AND RUN against a driver that actually drives them, byte-identical to fpc 3.2.2; pparser.pp (7823) is now ONE RTL DECLARATION away -- its three frontend walls fell to [[bug-p-a-parameterless-method-is-undefined-as-a-by-ref-argument]] (5 errors -> 2, 2026-09-06, held locally under the fleet push hold) and its ENotSupportedException wall then fell to one line in lib/rtl/sysutils.pas, and what is left is THREE errors from TWO causes, both in pparser.pp though the compiler blames pscanner.pp for both (a SIBLING call between two capturing nested routines of DoParseExpression at :2670, banked as [[bug-p-a-sibling-call-to-a-capturing-nested-function-gets-the-wrong-capture-actuals]] -- recorded here for a day as "a specialised generic", which it is not: that subject came from the diagnostic's near: window and THE WINDOW IS FROM THE WRONG FILE, so a wrong coordinate did not merely mislabel the wall, it SUPPLIED A SUBJECT; and a .Name lookup on a dynamic array's record element that poisons into a bogus CompareText arity report) -- see the note-to-self for the shapes. Throughout, the compiler misattributed pparser.pp:778 to pscanner.pp: right line, wrong file, a THIRD arrangement of this corpus's coordinate problem. pasresolver.pp (29660) remains unattempted. pastree took FOUR walls and the fourth arrived with NO COORDINATE AT ALL -- pascal26:0:, no file, no near: window -- because ASTLine was 0 for EVERY node in a usesd unit, a correct DWARF decision (keep the RTL out of the line table) that was also serving as the coordinate for every semantic diagnostic. THAT WAS NEVER A PASTREE PROBLEM: it is every semantic error in every corpus unit anyone has ever run, and the rungs kept producing usable numbers only because a PARSE error is reported off the lexer's own position instead. Behind that wall, Fields := nil on an array of <record> FIELD was TWO defects with the first hiding the second -- the assignment kind check refused it as cannot assign Pointer to record, and with the false reject gone the store zeroed four bytes over an eight-byte array handle and SIGSEGV'd. A FALSE REJECT WAS LOAD-BEARING and nothing had ever reached the lowering behind it. THE LARGEST UNIT WAS GREEN EARLIER THE SAME DAY, not merely next -- rungs 1-6 green, and rung 7's first wall is CLEARED (c4036925a, a const section ate the resourcestring that ended it). RUNG 7's LARGEST UNIT IS DONE as of 2026-09-06: pscanner.pp (5333 lines) COMPILES, LINKS AND RUNS -- a driver that constructs a TPascalScanner over a real file and pulls tokens prints fpc's exact output (ident: K / number: 42), from FOURTEEN walls. compiles WAS THE WEAKER CLAIM AND IT WAS WRONG BY ONE DEFECT: the unit reached zero errors and the driver then HUNG, because a Text handle reached through a FIELD was not recognised as a file handle at all -- TFileLineReader is exactly that shape -- so the console path took the call, WriteLn(F, x) printed the Text record to stdout and left the file empty at exit 0, and ReadLn(F, s) read stdin. Always drive the corpus unit, never only build it; a library that compiles and does nothing is the failure a corpus rung exists to catch and the one a wall count cannot see. Earlier in the same arc the rung reached pscanner.pp:2899, +2825 lines, through FOUR walls cleared (c4036925a, 87681a64a, and the bare-self bracket door): the resourcestring-eating const section, array of const in a procedural type, a user ENUM losing its name to one of the compiler's own internal records, and an array of const LITERAL parsed as a SET at a bare self-method call. TWO OF THE FOUR WERE INVISIBLE UNTIL THE ONE BEFORE THEM LANDED, and one of them was a wall that only LOOKED separate: no overload of ResolveStack matches at :1994 was a cascade of the TToken mistyping and vanished with it, so the wall count over-reported the defect count. AND THE SILENT/REFUSED RATIO IS THE THING TO CARRY OFF THIS RUNG. The set-literal wall was found only because fcl-passrc passes ['#0'], two characters, which a set cannot hold; the same defect with a single-character or integer element COMPILES and hands the callee Length 1026585632 where fpc says 3. A corpus finds the refusing member of a defect class and says nothing about the silent one, which is larger. Wall 3 IS characterised and filed: bug-p-a-property-default-value-clause-is-read-as-the-default-indexed-property-marker. default <named const>, default <expr> and nodefault are all refused (fpc compiles all three), AND the same one-line arm conflates two unrelated clauses -- a scalar default 16 sets propIsDefault, so declared BEFORE a genuine property Items[i]; default; it STEALS the slot and t[2] is refused where fpc prints 100. ORDER-DEPENDENT: write the indexed property first and the bug is invisible. --mimic-fpc IS REQUIRED for this rung and its absence presents as FPC_FULLVERSION has no integer value, an invocation error wearing the shape of a frontend bug. The source is at /usr/share/fpcsrc, NOT under library_candidates/, so this file's gitignored-corpus caveat does not apply to rung 7. SECOND DOOR: backlog-pascal/feature-pascal-corpus-passrc is the SAME RUNG at prio 30 and ready will offer it independently -- take this ticket, not that one. NO coordinate on this corpus is trustworthy: near: has been stale across a UNIT boundary, the line has been a CONSTANT equal to the file length, and the two have taken turns being the reliable one. Reduce from the SHAPE. The probe time RISES as the compiler gets further -- 75s -> 118s -> 454s -> 472s -- so a timeout tuned to the last reading cuts off the next success. library_candidates/ is gitignored: compare across checkouts by CONTENT HASH, never by commit. |
— |
| feature-pascal-corpus-fpc-testsuite | P | 65 | feature | Rung 1 of the Pascal corpus ladder: FPC 3.2.2's own tests/test suite (1447 .pp, fetched by tools/install_lib_candidates.sh fpc-testsuite, gitignored) run as a conformance corpus, burning the skip list one narrowed frontend bug at a time. Last full census 428 pass, 0 fail, 72 skip, 50 auto-gated of 550 at compiler 177239049b43 (frankS, 2026-09-09), 38 gap / 23 wontfix / 9 decided / 2 accepts-invalid. THE LAST +3 IS MINE and is decomposed exactly: tforin8, tforin24 and tforin2, burned by the RTL enumerator work in the same commit, and a per-row diff over all 550 shows those three and nothing else moved. Immediately before that: 425/0/75/50 at 44ef1e9937a4, commit 231ac5795, superseding 392/0/108 at 6ae3a04d3e5c (2026-09-06) and 377 at e929e720f. THE +33 IS NOT MINE AND ONLY PART OF IT CAN BE DECOMPOSED -- both halves of that sentence matter. Against the last committed per-row TSV (devdocs/progress/tstate/conformance.tsv, 4bb9aac9e, 2026-09-08 23:49, 420 pass) exactly FIVE rows moved, all skip->pass, each attributable by name: tclassinfo1 (5d29682a2), tgeneric91 (1c16d4523), tgeneric93 (004793f42), tgenfunc19 (f0aca9c59), toperator91 (81db1c1cc/637516cfd) -- none of them mine. The 392->420 stretch CANNOT be decomposed from what is in git: Track T's TSV has no snapshot between 2026-09-02 (349 pass) and 2026-09-07 (416), and the 392 falls inside that hole, so anyone quoting a per-ticket share of it is guessing. Also recorded so it is not re-derived: this session measured the same 425/0/75/50 at binary f22102f66298 at 13:06 (087cbba2b), and roughly twenty compiler commits have landed since -- equal totals are NOT evidence of an equal row set, and no TSV was written at 13:06 to check it against. skips by tag: 41 gap, 23 wontfix, 9 decided, 2 accepts-invalid, 0 untagged. PARK CONDITION SUPERSEDED: the 2026-07-10 park in the body is not a live block -- its three named tickets are in done/ and sole-A confirmation no longer exists in this repo. IT WENT UP ACROSS A RUNNER CHANGE THAT REMOVES ROWS: 109fbebb1 auto-gates a unit source (FPC's dotest compiles a unit standalone, pxx refuses, and a refusal satisfies %FAIL whatever the file holds, so those rows passed vacuously — 17 rows gated as unit-source here), and the generic-method work outran it. The 377 is a NET and has NOT been decomposed into newly-gated versus newly-passing; that needs the old runner at the old commit and nobody has run it (frankS's caveat, and they declined to guess). --report now writes a per-row TSV, so the next delta is a diff rather than a re-derivation. THE TWO blocked-by: EDGES ARE STALE AS BLOCKERS: erroraddr, TFPCHeapStatus and GetFPCHeapStatus all resolve from user code at 855356445cd7 and the heap counters are genuinely always-on (measured by delta, not by declaration), so erroru.pp — the suite helper whose absence gated tobject1 tstring2 tstring4 tstring5 texception3 as three unrelated-looking clusters — now compiles. Four of those five compile; tobject1 has a different wall behind it (bug-p-object-value-types-standard-meaning). The B rows stay open on their own criterion, which is a march over the separate FPC compiler-source corpus, so this row is gated by paperwork rather than by capability. Known trap on any burn, BOTH DIRECTIONS, because it is one error and not two: exit-clean is not correct — the runner compares exit codes, not output — and the inverse bites identically, a row whose whole assertion IS its exit code (halt(1)/halt(2)) says NOTHING when you only compile it, so both compilers build it reads as non-discriminating when the row is in fact discriminating and you never ran it. Ask what quantity the row ASSERTS in, and measure that one (frankD, 2026-09-09, on toperator6 — nearly reported as non-discriminating for exactly this reason). |
— |
| feature-pascal-corpus-generics | P | 65 | feature | Rung 3 of the Pascal OOP corpus: generics.collections (rtl-generics, FPC release_3_2_2) must COMPILE. Not done, not blocked, and the frontier moved THREE times on 2026-09-09: defaults:2729 -> defaults:224 -> collections:120 -> defaults:3250 -> past it. Measured at binary 5e00cec21466 (bab3814ad), two drivers, two walls -- QUOTE THE DRIVER BESIDE THE NUMBER: uses Generics.Defaults COMPILES AND RUNS as of 2026-09-09 (it printed defaults ok, rc 0, at binary 4b5ee0c8e11e) but COMPILING IS NOT WORKING: TComparer<LongInt>.Default answers nil where fpc answers a live comparer (-1/1/0), because a static class function's ADDRESS still carries pxx's hidden Self, so rtl-generics' dispatch through a plain function pointer shifts every argument by one -- the static-Self ABI was ONE wall and it is FIXED (bug-p-a-static-class-functions-address-carries-a-hidden-self, done, b0d53c73a) and it did NOT clear the nil. The wall actually under it is measured and filed: bug-a-a-hand-built-com-interface-cannot-be-called -- pxx's interface value is the INSTANCE with the IMT recovered from its RTTI blob, FPC's value IS the IMT pointer, and rtl-generics reaches every comparer through a hand-built table. 60-line repro, and the positive control is the exact mirror: IFoo(Pointer(anObject)) works under pxx and dies with RTE 216 under fpc -- the wall was a record's static class function registering Self differently at its declaration and its implementation, so the impl minted a second proc row and a specialized body bound to the bodyless one; uses Generics.Collections NOW reaches unknown type: PT at collections.pas:120/123 (872 mints -> 203) since the runaway was fixed; before that it reached too many deferred specializations, with TEnumerator$PT minted 55 times. THAT SECOND ONE IS NOW DIAGNOSED and it is not the older PT defect on its own: it is a NON-CONVERGING SPECIALIZATION-NAME FIXPOINT -- p.mint shows a strict ladder of 10 aliases at each of 55 rungs, rung N+1 taking rung N's mangled alias as its argument, and p.nspec naming the pump: rung N+1's substitution IS rung N's alias. Raising MAX_SPECIALIZATIONS to 1024 does not help: it reaches token character pool overflow instead. FIXED (bug-p-a-specialization-alias-grows-one-segment-per-round-when-an-argument-never-resolves, done): the cycle was a nested class's method impl matched by the LAST component of its qualified path, so specializing the unit-level TEnumerator<T> scanned TQueue's nested body and minted a TQueue nobody asked for. The wall left is frankZ's unresolved PT. Closed on the way here: bug-p-a-bare-method-name-in-argument-position (frankH), a forward ^T in a nested type section (frankZ), a specialized body materialising where it is visible (frankH), and bug-p-a-generic-method-implementation-is-attributed-by-name-not-arity (frankS). THE TWO STAGINGS ARE THE SAME FILE -- generics.collections.pas is byte-identical between library_candidates/rtl-generics and /usr/share/fpcsrc/3.2.2 (md5 1010a887c20dc546215749ca46c5a773, 110423 bytes) -- so every wall line number here, the 2026-08-30 table included, is the same coordinate system. Rungs 1+2 green: fpcunit runs, fpjson 203/203. Claimed by frankS. |
bug-a-a-hand-built-com-interface-cannot-be-called, bug-p-a-class-nested-type-as-a-specialization-argument-resolves-at-unit-scope |
| feature-pascal-corpus-oop | P | 75 | feature | Pascal OOP corpus — real libraries that hammer classes/interfaces/generics | — |
| feature-pascal-management-operators-copy-and-addref | P | 30→75 | feature | BOTH HALVES DONE 2026-09-06 (frankA), ABOVE 8 BYTES. class operator Copy(constref src; var dst) dispatches at the record-assignment lowering; class operator AddRef dispatches at the BY-VALUE PARAMETER copy. Both match fpc 3.2.2 byte for byte (test_mgmt_operators_copy at three assignment sites; test_mgmt_operators_addref across by-value/const/var; test_mgmt_operators_addref_nonlvalue_arg across the argument SHAPE) and all are refused outright on the pin. THE BY-VALUE PARAMETER HAD NO LIFECYCLE AT ALL, not merely a missing AddRef -- the copy is an skParam and the parser-side wrapper walks skLocal/skGlobal only, so the callee-side Finalize was missing too and AddRef could not land alone; both are now emitted at the caller's private temp, Finalize FIRST (pre-order) on the post-call queue. THE HOOK IS KEYED ON WHETHER var/out/const WAS WRITTEN, NEVER ON WHETHER A TEMP WAS BUILT: a temp is built for four different reasons and only three are by-value. Measured both directions -- a const parameter given a NON-LVALUE takes a temp and fpc runs no operator (the first cut fired there: callee read 107 against fpc's 7), while a by-value parameter given a non-lvalue takes the SAME arm and fpc DOES run AddRef. REFUSED AT OR UNDER 8 BYTES, by name and by size: the backend pushes the record as machine words so the copy has no address: forcing a temp there was MEASURED to break the ABI (callee read 4311096 for 107) and was reverted for an explicit refusal. RESIDUAL, NOT MINE: a genuine var record parameter on an INTERFACE or virtual; abstract method has no trustworthy discriminator here, because ProcParamExplicitByRef is never written by the parameter parsers in pasparser_decl.inc (frankB, 2026-09-06); the const half is excluded independently via ProcParamIsConst, which IS written at those sites, and the guard tightens for free when that column is fixed. CORPUS: tmoperator8 RE-MEASURED at fe2ef24ce, not assumed -- it moved from line 63 (the AddRef refusal) to line 143, where it now stops on the DYNAMIC/MULTI-DIMENSIONAL ARRAY refusal, so the row is advanced but still not cleared and its remaining blocker is feature-pascal-management-operators-nested-and-array. ESTABLISHED 2026-09-06 AND THE ANSWER WAS NO: the two hooked assignment arms were NOT the whole population. Censused 14 copy shapes against fpc; 13 agreed, and a whole-record assignment of a record CONTAINING a Copy-operator field ran nothing -- filed as bug-a-a-whole-record-assignment-does-not-run-a-contained-fields-copy-operator, and FIXED there 2026-09-07: the copy is punched around the operator fields (IRRecCopyWithHoles) and a whole-ARRAY assignment unrolls one call per element (IRArrayElemCopyOps), across all three block-copy arms. Initialize/Finalize reached that field all along (the scope desugar walks the field table) while Copy did not (the IR hook asks for an overload on the OUTER record); the two mechanisms still have different reach and that is the standing smell, but they now agree on the answer. ONE SHAPE STILL DIVERGES and has its own ticket: a record mixing an ARC field with an operator field -- bug-a-a-record-mixing-an-arc-field-with-a-copy-operator-field-skips-the-operator. THE COPIED VALUE IS CORRECT in all of these, so no expect_same row over the census programs can see it; the committed fixtures discriminate only because their operator PRINTS and deliberately leaves one field alone. |
— |
| feature-pascal-management-operators-nested-and-array | P | 35→75 | feature | FIXED ARRAYS ARE DONE 2026-09-07 (frankA); ONE ARM LEFT AND IT IS NOT THIS PASS'S. Reached and managed now: a managed record through a FIELD at any depth, through an ELEMENT of a fixed array SYMBOL, through an ELEMENT of a fixed array FIELD, and -- new -- through a MULTI-DIMENSIONAL fixed array in either position, to any dimension count. Storage is flat row-major and the recorded extent is the FLAT element count, so one loop walks any dimensionality; what changes with the dimension count is the INDEX SPACE, and that is the whole finding. MEASURED, not derived: a synthesised single-subscript AN_INDEX over a 1-D array is lowered in SOURCE space (the low bound is subtracted, so array[2..3] is walked 2..3) and over an N-D array in FLAT space (nothing is subtracted, so it is walked 0..count-1). Walking an N-D array from its own low bound writes one element PAST the array and leaves the first uninitialized -- array[1..2, 5..7] printed body 001234 against fpc's 012345 and finalized a sixth element holding the neighbouring scalar field. A 0-BASED FIXTURE CANNOT SEE ANY OF THAT: with the dimension guard removed, array[0..1, 0..2] walked flat matches fpc element for element, because a flat index and a dimensional index coincide at origin zero, so every multi-dim fixture here declares non-zero low bounds. Verified against fpc 3.2.2 byte for byte on four shapes no two of which share a mechanism (2-D symbol, 2-D field beside a scalar neighbour, 3-D field with three non-zero lows, 2-D field whose element holds its own 1-D array field), on all five runnable targets. STILL REFUSED AND DELIBERATELY NOT THIS TICKET'S WORK: a DYNAMIC array, symbol or field. Measured against fpc 3.2.2, its Initialize runs INSIDE SetLength on elements that come into existence and Finalize inside it on ones that stop, with only the survivors finalized at scope exit -- so a scope-entry loop over Length() initializes ZERO elements and never sees one created later. That arm needs [[feature-a-record-rtti-descriptors-for-initializearray-and-finalizearray]], not a desugar, and the refusal text now says so. CLASS fields left for [[feature-pascal-management-operators-on-a-class-field]]. ORDER RULES UNCHANGED AND STILL MEASURED: across NESTING levels Initialize is POST-order and Finalize PRE-order; across ARRAY ELEMENTS both run ASCENDING, in flat storage order for a multi-dimensional one. CORPUS: tmoperator7 is still not cleared -- its array is DYNAMIC. | — |
| feature-tls-provider-abstraction | B | 53 | feature | HTTPS WORKS, SYNC AND ASYNC, THROUGH TWO BACKENDS, AND THE TICKET HAD NO SUMMARY UNTIL 2026-09-05 -- a p53 ticket in working/ with no status, owner or summary in its frontmatter, so the one part everyone reads did not exist. Six slices landed 2026-06-25: lib/rtl/tls.pas ships the backend-neutral vtable and registry (fails CLEANLY with tlsError when no backend is registered, never crashes), http.pas routes https:// through it across ALL FOUR transports (blocking one-shot, async one-shot, keep-alive, async pool -- reuse keyed on host:port:SCHEME so an https connection is never handed to a plain request), then the OpenSSL backend, async handshake, certificate verification with a trust store, and server role via SSL_accept with OpenSSL<->OpenSSL interop. The NATIVE TLS 1.3 backend landed 2026-08-01: ed25519 + rsa_pss + ecdsa_p256, chain verify, kTLS-TX with a Pascal fallback; it is CLIENT ROLE ONLY and answers native TLS 1.3: server role is not implemented (tls13_native.pas:295) -- the OpenSSL backend has the server side, the native one does not. 2026-09-01, TWO findings worth more than the code: (a) the blocked-by: [feature-tls13-from-scratch] edge was FALSE, not stale -- that ticket is deliberately parked in rainy-day/ and ready keeps a ticket whose blockers are RESOLVED, so parked-not-resolved suppressed this p53 ticket from the Track B queue entirely while the native backend it was supposedly waiting for had already landed; (b) this ticket claimed async https occupies a thread for the length of a handshake and the truth was worse -- ASYNC HTTPS DID NOT WORK AT ALL. RecvN tested if got <= 0, swallowing PAL_NET_EAGAIN as EOF, so a would-block was reported as no ServerHello (connection closed) about a connection that was open and healthy; SendBytes had the twin defect and was quieter, ignoring its result so EAGAIN sent nothing and a short write truncated a record. Fixed, with the coroutine stack canary now checked at EVERY yield rather than only at completion (a silent SIGSEGV became fatal: coroutine stack overflow). NO STATE MACHINE was built and that is measured, not preferred: the scheduler is STACKFUL, scheduler.WaitIO ends in a real __pxxcoswitch. 2026-09-06, THE COROUTINE-STACK HALF IS DONE AND THE NOTE THAT ASKED FOR IT WAS WRONG ABOUT WHY. It said the canary cannot catch an overflow that faults before it can yield and that a guard page would fix that. Measured: a guard page fixes NOTHING there -- a deep overflow already faults, because a GetMem stack sits next to unmapped space. The real gap was the opposite shape and nothing had named it: the canary is ONE WORD, so a single frame larger than the remaining stack STEPS OVER IT, leaving the canary reading correct. On an 8 KB stack, frames of 10/12/14 KB all exited 0 having written below the stack base while an 8 KB frame faulted -- A LARGER OVERFLOW CAUGHT LESS OFTEN THAN A SMALLER ONE, because what decided it was heap layout. Landed: a 64 KB PROT_NONE guard region (address space only, zero RSS), CO_RED writable slack between canary and guard, and an sp-range check at every yield. The slack exists because the guard REGRESSED the canary without it -- gradual overflow died on the guard before reaching the yield that would have named it, rc 217 with a message became a bare rc 139. Outcome is now monotonic: fits -> runs, over -> named message (canary or sp, two different strings), far over -> faults at the guard. test_costack_guard.pas has three arms and FAILS TWO OF THEM against the pre-change scheduler. HONEST LIMITS, both measured: a frame that RETURNS before yielding is invisible to both checks (contained in the scheduler's own slack, not a neighbour, but undiagnosed), and a single frame clearing 64 KB still escapes the guard -- only stack probing closes that. STILL OPEN: the native backend refuses the SERVER role (tls13_native.pas:295). THE STREAMED-BODY GAP IS CLOSED: a 2 MB body over https on BOTH paths, asserting LENGTH and a byte SUM derived from the served file each run, with a third row fetching a SMALL file under the big file's expectations to prove the comparison REJECTS -- a length check that is silently comparing nothing passes every positive row. The code was already right; both paths returned len=2000000 and the exact sum first run, so what was missing was a test that could fail, not a fix. THE make lib-test cannot run to completion for ANYONE NOTE BELOW IS STALE as of 2026-09-05: crtl_reachability passes (148 headers, 66 modules) and the suite runs. |
— |
| perf-a-every-return-releases-every-managed-local-even-the-untouched-ones | A | 70→95 | perf | MEASURED, two independent methods agreeing. EmitManagedLocalCleanup releases EVERY managed local at EVERY return, whether or not that path ever touched it, and the sweep is emitted INLINE at each return. Two separable costs, and conflating them will misdirect the fix: (1) RUNTIME — the full sweep EXECUTES on every call, measured linear at 3.87ns per local per call even when every slot is nil, which is ~4.5% of a compile for ParseFactorCore's 532 locals alone; (2) CODE SIZE — 308,112 release call sites binary-wide = ~36% of the compiler's 10.2MB .text. A shared epilogue fixes (2) and NOT (1): the sweep still runs in full. COST (2) IS NOW LANDED on all six flat-code backends, one commit each (x86-64 50e25f5f0, i386 3d7cde305, arm32 4a1a80184, aarch64 5f89103c9, riscv32 b1554c59a, xtensa Call0 dde109a7a); wasm32 is not a seventh and has its own ticket. Measured like-for-like on one instrument with TargetHasSweepThunk forced False for the control: compiler.pas release sites 349,581 -> 43,508 (8.03x), code= 11,075,352 -> 7,376,664 (-33.4%), artefact -31.9% -- and the control lands within 0.07% of the 349,322 counted independently by objdump, two instruments that fail differently. THAT DISSOLVES THE T=400 THRESHOLD rather than confirming it: at the measured +5.06 B/site the inline nil-test on EVERY site now costs +2.98% of .text against +15.97% before, i.e. a third of what the T=400 gate cost without the sharing, while covering 100% of sites instead of 56.6%. That recommendation -- build it UNGATED, keep a threshold in reserve only if the BUILT artefact's size cost came in materially above 2.98% -- was followed and its condition did not fire: measured +1.39%. THE BRANCH-WIDTH CAVEAT IS CLOSED and is structural, not a frequency: the per-slot sequence is mov+call with the argument already in rax, so a nil-test skips exactly one 5-byte call rel32 and the displacement is 5 on every site in the binary -- cost is exactly 5 B/site, and a 7.23% figure for site-to-site gaps over 127 bytes measures a DIFFERENT quantity (gaps between sweeps, which the branch never spans). THE CHEAP HALF OF (1) IS BUILT ON x86-64 (2026-09-22, frankb-8e): the inline nil-test at the release call site, ungated, on the scalar AnsiString arm -- 4.367 -> 1.886 ns/slot/call, 56.8%, against the 55.8% the calibrated model predicted, and +1.39% of artefact against the +2.98% predicted, so the frame-size threshold's own trigger condition is measured and NOT met and T=400 is retired rather than deferred. The ratio transfers between boxes and the absolute does not: the control marginal reads 4.367 here under load against 3.821 on an idle box, both rows kept. WHAT REMAINS OF (1) IS THE EXPENSIVE HALF AND IT IS STILL UNSTARTED -- the full sweep still RUNS on every return; this removed the cost of ASKING about a slot, not the asking, and per-path liveness over compiler-minted temps is untouched. ALL SIX REGISTER BACKENDS NOW CARRY IT (2026-09-22, frankb-8e), one commit each with the other five byte-identical as the blast-radius bound: x86-64 022739dce, i386 2aa7e7159, arm32 a0f4facd8, aarch64 ec82fc0de, riscv32 63fdda88d, xtensa 1d0ee74fe -- 3 to 8 bytes per site depending on the ISA, and the split between the three arms needing an ABI argument for clobbered flags and the three needing none is exactly whether the architecture HAS condition codes. The ESP size question the middle commits deferred was measured before riscv32 was written (608e0f53c): a bare image is 15x less site-dense than compiler.pas, at whose density the ungated decision was already taken, so no ESP-specific gate is needed. Also still open: the non-string arms (SXR_VAR/SXR_OBJ/interface/array -- a string-slot decomposition does not transfer to a call that does real work), and the prologue nil-init store (0.526 ns/slot on all seven targets, which no fix on this ticket reaches). (1) needs per-path liveness. (2) applies to FIVE backends: wasm32 already has the shared epilogue because structured control flow forced it (franka-29, measured), which makes it an existence proof rather than an exception. (1) applies to all SIX. MEASURED 2026-09-06 (was flagged unexplained): the model reproduces 3.772 against 3.821 real, and it decomposes as prologue nil-init store 0.526 (14%) + epilogue load 0.262 (7%) + THE CALL/RET PAIR 2.984 (79%). franka-29 was right that the helper body is cheap -- that body costs 0.879 inlined; the cost is getting there and back. An inline nil-test at the call site takes it 3.772 -> 1.667, a 56% runtime saving with NO liveness. MEASURED 2026-09-07 BY TWO METHODS THAT FAIL DIFFERENTLY: ~98% of the swept slots are COMPILER-MINTED UNNAMED TEMPS, not locals anybody wrote -- 98.4% by direct count (ParseFactorCore: 10 named vs 609 unnamed tk=23 syms in the IR) and 98.2% by subtraction (757 released slots off the binary, 14 declared off the source). So per-path liveness over USER locals addresses 14 of 757 slots, 1.8% of the worst sweep, and cost (1) is a question about temps. NOT settled: whether temps can be skipped -- :13838's 'does not outlive the statement' is about the temp's VALUE, while the release loop needs a claim about OWNERSHIP of what it references, and skipping without that is a leak no value assertion catches. Note the prologue store is a THIRD cost that neither fix (1) nor (2) touches, and it is PER-SLOT ON ALL SEVEN TARGETS (measured 2026-09-07 by return-count separation, no disassembler needed) -- so one liveness analysis serves both halves. wasm32's release term is 0.062 B/slot/return, the first actual MEASUREMENT of its shared epilogue rather than an inference, and it still pays the full per-slot prologue. WARNING: the compiler's code= is page-quantised (65536 on aarch64, where it reads 196376 for both N=4 and N=532) and on wasm32 reports 3582 flat while the code section grows 13707 bytes -- use artefact size, never code=, for anything per-slot. WHOLE-PROGRAM, MODEL-FREE (2026-09-07): the thunked build compiles compiler.pas 2.04% and 2.51% faster than the inline build across two runs -- the SAME PROGRAM built two ways, cmp-gated so a pair can never be reported for builds that disagree. That is the -31.9% size win showing up as SPEED, with the call/ret cost INSIDE the figure rather than absent from it; it does not decompose them and nothing here lets it. Found from the Track P ticket perf-p-parsefactorcore-walks-a-92-arm-name-chain-per-factor, whose premise this refutes for the third time. |
— |
| perf-n-one-computed-getattr-in-any-imported-module-boxes-every-method-in-the-program | N | 45→95 | perf | PyModuleHasComputedGetattr is deliberately COARSE: when it is true, PyMethodUsedAsValue returns true for EVERY name, so every method in the program takes the function-object ABI (variant params, variant result) and pays boxing. Until 0c508e507 (2026-09-20 19:23) it scanned the MAIN FILE only, so in practice the coarse arm almost never fired. That commit widened the scan to every Python source range -- required, because a computed getattr in an imported module was a SIGSEGV, rc=139, two fixtures -- and the coarse arm now fires for any program with one computed getattr ANYWHERE in its imports. lekkerzeilen has exactly one: lekkerzeilen/gfx.py:349, handle = getattr(self, attr). Measured cost on the demo, now CONTROLLED (franks-5b, 5b1045dad, one tree, one CWD, both binaries in-tree, only the compiler differing): code 12047464B -> 12159489B, +112025B, +0.93%, with procs IDENTICAL at 11594 -- so no wrappers were added and the growth is boxing inside existing bodies. The uncontrolled estimate filed first landed on these numbers to the byte. SEPARATELY AND DO NOT CONFLATE THE TWO: the same controlled run makes the demo COMPILE 20.3% faster (130.70/130.95s -> 104.15/104.19s, interleaved min-of-N), because the range also contains the memoisation 1fbe6e104. That is BUILD time. THE RUN-TIME COST IS NOW MEASURED (franks-5b, 2026-09-22) AND IT IS 4.01x ON A METHOD-CALL-DOMINATED SYNTHETIC -- min-of-5 interleaved, boxed 8.69 s against native 2.17 s over 6,000,000 method calls, one define apart from one tree, the ON arm BYTE-IDENTICAL to compiler/pascal26. THE ALLOCATION ROW IS THE INFORMATIVE ONE: seven allocations in BOTH arms, identical, so the 4x is ABI width and variant tag dispatch and NOT the heap -- chasing the allocator would find nothing, and 'make the boxed path cheaper' means dispatch and parameter passing. Code +103,020 B (+25.8%) with procs identical at 2232. CONTROLS: on a program where the arm cannot fire the two compilers emit BYTE-IDENTICAL binaries, so the define is confined to this arm; with the getattr present they differ; both arms print the same answer. READ IT AS A CEILING, NOT AS A PREDICTION FOR THE DEMO -- the fixture's loop is almost nothing but method calls, lekkerzeilen's frame is not, and the demo has still not been run at HEAD. The switch -dPXX_COARSE_GETATTR_OFF is committed and documented at the arm so the price is re-derivable; it is TIMING ONLY and suppresses the crash fix. AND NARROWING IS NOW BLOCKED BY A CRASH: bug-a-the-nilpy-print-promo-argument-temp-is-never-zero-initialised segfaults on pin v418 and at HEAD -- at EVERY -O level, not just the default; the other levels only look clean because the fault depends on stack garbage (frankb-8e, 2026-09-22) -- and BOXING MASKS IT -- when this arm fires the promo-int pair becomes tyVariant and the crash disappears. The first version of this benchmark segfaulted for exactly that reason. Narrowing this arm turns working programs into segfaults until that lands. NOT a correctness bug and NOT a candidate for reverting: the widening is what stops the crash. The question is whether the coarse arm can be narrowed without reopening it, and the honest answer today is that it probably cannot be narrowed by NAME, because a computed getattr is precisely the case where no token spells the name. |
bug-a-the-nilpy-print-promo-argument-temp-is-never-zero-initialised |
| refactor-a-carve-the-nilpy-arms-out-of-the-shared-pascal-argument-loops | A | 45 | refactor | The last NilPy references in the shared Pascal parser, and they are NOT where the previous carve looked. ParseFactorCore already hands NilPy expressions to PyParseFactorCore and Exits at pasparser_expr.inc:521; every remaining site is BELOW that line, guarded by isNilPy rather than PyExprMode -- NilPy arms threaded through the shared ARGUMENT loops (keyword binding, *args unpacking, keyword-driven overload promotion), which the expression hook never sees. THREE SPECIES, only one of which is a move: a shared helper wearing a Py prefix, a semantic predicate needing a neutral hook, and the argument loops needing one NilPy argument-list parser. Treating all three as species 1 is how the 176 stubs the parent rejected get written by accident. Progress is one command but the target is NOT zero -- the census counts UNDEFINED symbols under the flag, so a NilPy arm whose helper lives in a shared file is invisible to it: pasparser_proc.inc carries nine real isNilPy arms and the census scores that file at ZERO. fpc -dPXX_NO_NILPY reported 279 sites at filing and 209 now, after five steps: StoredName moved to util.inc (closing the compiler's only frontend-to-frontend dependency, cparser.inc -> pyparser.inc) the first REGION carve (six references, a six-line hook), ParseFactor's NilPy head (34 sites, two hooks), and the two DEAD-ARM deletions -- the shared expression and statement call loops carried thirteen arms guarded by isNilPy where the question was PyExprMode, which could not fire at all (7314fab2b, 23c4552af). Report that ratio per region -- near 1:1 means you have hit a species-2 site and should design the concept-level hook instead. |
— |
| refactor-a-one-program-driver-prologue-for-every-frontend | A | 45 | refactor | TEN OF TWELVE drivers now reach their parse through EmitProgramPrologue (frontend_prologue.inc); NilPy landed 2026-09-02, verified by 24 before/after rows (12 .npy tests, plain and --threadsafe, identical program output and identical compiler messages), eleven other-frontend binaries byte-identical, and three cross targets identical under qemu. LEFT: the C driver, blocked on merging its five per-arch call-main entry chains with EmitProgramEntryForTarget; and the PASCAL driver, blocked on a question this ticket used to call pure de-duplication -- the Pascal driver does NOT call RegisterEmittedStringRuntimeForwards, it registers a larger target-conditional SUPERSET inline, and RegisterProc is not idempotent, so passing wantAnsiRuntime=True would append ~40 duplicate proc rows. Decide that before converting, not during. The drift this deletes is measured, not felt: adding ONE new stub in 187a372a6 required four hand-written call sites, one per unconverted driver. | — |
| refactor-p-five-dispatch-sites-for-one-named-type-cast | P | 35 | refactor | Five dispatch sites decide what SomeName(expr) casts to — FOUR since 1df943481 |
— |
unfinished (18)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| bug-b-reportlab-mimic-multi-font-heap-corruption | N | 30 | bug | ROOT-CAUSED to bug-p-constructor-with-a-defaulted-variant-param-corrupts-memory and largely fixed by a workaround. The original font-count table was WRONG — an artefact of small samples against an intermittent fault. A rarer residual remains | — |
| bug-n-a-local-named-after-its-own-def-aliases-the-function-result | N | 60 | bug | A NilPy local whose name equals its enclosing def's name aliases the function result instead of being an ordinary local: def mode(label): tonic, mode = label.split(' '); return tonic, mode returns ('C', None) where CPython returns ('C', 'minor'). Silent wrong value, no diagnostic. |
— |
| bug-nilpy-shared-nonlocal-frame-cell-is-never-freed | N | 40 | bug | A nonlocal capture's shared frame cell (pycell_new) is never freed — ~23 B per escaping closure, the only closure shape still leaking now that the bound-fn object is refcounted |
— |
| bug-o-uforth-blocktest-runs-slower-under-pxx-than-under-cpython | O | 25 | bug | uforth's blocktest word set takes 413s compiled by pxx against CPython's 196s interpreting the same source — the AOT compiler is 2.1x SLOWER than the interpreter it is differentially tested against, and it is now the pole of two test tiers | — |
| docs-devnotes-ai-assisted-build | D | 50 | docs | Developer notes: how this was actually built (AI-assisted, and honest about it) | — |
| feature-a-build-a-reduced-compiler-by-selecting-frontends-and-targets | A | 25 | feature | Build-time selection of frontends and targets. Thirteen omission defines ship. PXX_NO_NILPY now has its INCLUDE GUARDS, driver refusal, .py-module refusal and ParseArgExpr fallback landed (byte-identical in the default build) but DOES NOT BUILD YET and is deliberately not advertised. The carve campaign it was parked behind has LANDED and did not finish the job: re-measured 2026-08-31, 134 symbols / 279 sites remain, down from 176/426 -- concentrated in five routines of the shared Pascal expression chain, as NilPy arms inside the shared ARGUMENT LOOPS (guarded by isNilPy, BELOW the PyParseFactorCore hook, which is why that hook did not close them). Parked again behind refactor-a-carve-the-nilpy-arms-out-of-the-shared-pascal-argument-loops. Also, unchanged and still the headline: omitting frontends is NOT the size lever -- nine frontends buy 4.4%, three host backends buy 20.7%. |
refactor-a-carve-the-nilpy-arms-out-of-the-shared-pascal-argument-loops |
| feature-c-diagnostics-name-the-module-they-are-in | C | 40 | feature | A Pascal diagnostic now prints in: <path> when the error is in an include or a usesd unit. The C frontend has the same information already — CModRange* is populated in every build, not just under -g — and prints nothing, so an error in a crtl module or an included header still reports a bare line number. |
bug-a-c-diagnostics-cannot-name-a-header-only-the-module-that-included-it |
| feature-dynamic-compiler-tables | A | 45 | feature | INCREMENTAL CONVERSION ON MASTER, PROVEN AND MID-FLIGHT, held by frankH. The compiler held ~305 fixed parallel array[0..MAX_*-1] tables in defs.inc; each is a hard ceiling a large translation unit can hit (sqlite''s 257k-line amalgamation broke MAX_TOKENS) and together they dominate the compiler''s BSS. DONE: Tokens, Syms, UField, IR, AST, Code, LoadFileBuf, CPrepChars, Data, Strs, Fixups+FixupPCRel+FixupPicDelta. STILL FIXED: TokChars (STRING_CAP, 8 MB), LabelFixupPos/Target (MAX_IR, 1 MB each), UCls* (MAX_UCLASS), and Procs DELIBERATELY. THE TICKET''S REAL VALUE IS ITS METHOD, and it is not optional: BEFORE CONVERTING A FAMILY, GREP ITS MAX_ NAME ACROSS compiler/** AND READ EVERY HIT -- deleting a cap does not delete the code that assumed it, and four sites had taken MAX_X to mean a number the count can never reach, one of them an out-of-bounds stack write in IRVerify which runs on every body (bug-a-dynamic-tables-left-their-fixed-size-shadows-behind). 2026-09-05: LoadFileBuf converted, worth 8 MB of BSS (106842604 -> 98454060) and a measured before/after correctness fix on the FPC-seed path -- see that section; it also stopped BORROWING STRING_CAP, which is the token char pool''s capacity with ~40 overflow checks against it, so one constant had been sizing two unrelated things. 2026-09-06: CPrepChars converted, another 8 MB (98454060 -> 90066100), and this time the cap was PROVEN REACHABLE -- 30000 macros with ~430-byte values trip this table''s own C preprocessor text overflow, while an 18.5 MB file of 300000 SHORT macros trips MAX_CPREP_MACROS instead and would have read as unreachable: TWO CAPS CAN BE IN RANGE OF ONE INPUT and the diagnostic string is the only thing that says which axis you tested. 2026-09-06: Data converted, 2 MB (90066100 -> 87985348), and it settled a coupling the ticket''s own grep method CANNOT see: TWO TABLES CAN SHARE A CEILING WITHOUT SHARING A CONSTANT. Every string literal costs 32 bytes of managed-string header plus its 8-aligned text, so MAX_DATA (2 MB) capped the string table at ~52108 entries and MAX_STRS (65536) WAS UNREACHABLE -- Error(''string table overflow'') was a guard that could not fail. Proven by the SAME 66000-literal input answering data overflow before and string table overflow after. Converting Strs was worth nothing before this and is load-bearing now. The conversion also segfaulted first: five byte runs and two constant-offset writes reach Data with no overflow check at all, because a fixed bss array never needed one. METHOD ADDITION: after converting a table, enumerate its WRITE sites, not its CAP sites -- the cap sites were already thinking about the limit. 2026-09-06: Strs converted too (1.5 MB, 87985348 -> 86412492) -- FIRST table here whose growth carries a MANAGED field (TStrEntry.Text is an AnsiString), probed before converting. One input crossed three states and that is the whole proof: 66000 short literals said data overflow, then string table overflow, then compiled and RAN. 2026-09-06: the fixup family converted (2752488 bytes, 86425036 -> 83672548), all THREE parallel arrays through ONE helper so there is no site that can grow the table without the columns. THE CHAIN IS NOW COMPLETE FOR ONE INPUT SHAPE AND IT TERMINATES: 52200 literals data overflow -> 66000 string table overflow -> 40000 a 22.24s TIME ceiling -> 200000 fixup overflow -> 500000 COMPILES in 14.04s at 992 MB peak RSS and runs correctly. Each conversion revealed the next ceiling and the reveal is the only way any were known reachable; the terminal state is memory, not a constant. AND THE ORPHANED COMMENT KNEW SOMETHING: the 2026 raise of MAX_STRS from 8192 to 65536 is what MADE that guard unreachable, because 65536 sat 13000 above anything MAX_DATA could fund -- RAISING A CAP WITHOUT CHECKING THE RESOURCE IT IS DENOMINATED IN IS HOW A GUARD STOPS BEING ABLE TO FIRE. LANDMINE ON THE LIFTED CAP: InternStr dedups by LINEAR SCAN, so it is O(n^2) -- 5000/10000/20000/40000 literals take 1.29/2.48/6.30/25.69s. Removing the cap swaps a hard error for a time ceiling, which is strictly better but is NOT the same claim; a hash index is the next change. STILL STANDING FOR TokChars: STRING_CAP also sizes the SHORTSTRING TYPE (ast_syminfer.inc:151, ir.inc:2703), so that constant must be SPLIT before TokChars can be converted at all. And the pattern is realloc PRESERVING INDICES: a free list or a compaction pass is OUT OF SCOPE, because it turns zero-init sentinel columns like AliasEnumId from inert into stale-fail-open. |
— |
| feature-nilpy-cpyext-c-api-from-source | N | 65 | feature | cpyext: compile a CPython C extension's SOURCE against our own Python.h |
— |
| feature-nilpy-enum-class | N | 62 | feature | from enum import Enum — enum classes are not supported |
— |
| feature-nilpy-thirdparty-libraries-as-targets | N | 65 | feature | META: third-party Python libraries as pxx targets — classify, then compile | — |
| feature-nilpy-user-defined-decorators | N | 68 | feature | A user-defined decorator — the ordinary @wrap over a def, not one of the four recognised names — is refused at parse time: "unsupported decorator (only @dataclass and @overload)". The decorator list is a NAME whitelist, so nothing a program declares itself can appear in it. |
— |
| feature-pal-esp-posix-fd-semantics | B+S | 20→30 | feature | Exact POSIX fd semantics for the ESP PAL over IDF VFS, replacing the newlib-stdio backend. UNBLOCKED 2026-09-02: bug-a-emit-obj-ignores-external-name-and-emits-the-pascal-identifier is in done/. Acceptance still needs a C3/S3 link-and-run, which this box cannot do. The baseline the ticket asked for IS NOW LANDED, but NOT in the shape the ticket suggested: a host-side --platform=esp test would pin the STUB, because the whole stdio/IDF arm is under {$ifdef PXX_PAL_ESP_IDF_TARGET}, set only for CPU_XTENSA/CPU_RISCV32 -- so on the host every PAL file call returns PAL_ERR_UNSUPPORTED and such a row would pass identically before and after the rewrite. The landed baseline builds the fixture for both ESP targets and asserts the newlib stdio symbol imports, with the host build (0 of 7) as its negative control. | — |
| feature-rust-option-type | R | 0 | feature | Rust frontend: Option<T> — the stage-2 rung of the chess ladder |
— |
| feature-target-wasm | A+B | 25 | feature | Emit wasm32 modules from the shared IR: new backend + module writer + WAT text emitter (Track A, new files), plus lib/rtl/platform/wasi (Track B). Two shared-file escapes: VMT slots hold code addresses (wasm has none — they become table indices) and exceptions are a hand-rolled setjmp/longjmp that does not port. The wasm branch MERGED into master and is gone — verified 2026-08-31, origin/wasm is an ancestor of origin/master with an empty diff — so this is dispatchable on master like anything else, and the previous NOT-DISPATCHABLE/do-not-claim imperative was false and being obeyed by the ranker. |
decide-how-the-sys-intrinsics-reach-wasi-when-the-compiler-links-no-pal |
| perf-p-parsefactorcore-walks-a-92-arm-name-chain-per-factor | P | 30 | perf | RE-MEASURED at HEAD -O2 (frankZ, 2026-09-04): the share has NOT dropped (9.92/9.94/10.35% over three runs vs the original 9.44%), so 440c822e6 did not remove it — but the premise is refuted for a THIRD time and the ticket is now mostly in the wrong lane. Disassembled, ParseFactorCore is 1,146,385 bytes of which 84% is managed-local TEARDOWN: exactly 150 runs of exactly 532 AnsiString releases (532 locals x 150 return points), carrying 36.1% of the function's samples. The 92-arm walk this ticket is NAMED for is 114 CaseEqual call sites carrying 3.2% of the function's samples = ~0.32% of a compile; a perfect hash dispatch has a generous ceiling of ~3% only if it also took all of CaseEqual's 3.1% body, and it carries the three documented hazards (name reassigned at 8 points, 25 duplicate names, the arms are not a ladder). The teardown is bigger, is Track A codegen, and is filed as perf-a-every-return-releases-every-managed-local-even-the-untouched-ones. WHAT IS LEFT FOR P is the ~0.3-3% dispatch question, ranked below its own hazards — not the 9.4% this ticket was opened for. | — |
| refactor-a-the-durable-param-row-is-hand-copied-on-three-registration-paths | A | 45 | refactor | WRITTEN AND PARKED 2026-08-31, one step from done: the collapse builds and self-hosts (0a7978a21cbc, 1 round) with both guard tests unchanged, and is committed as a PATCH at devdocs/progress/patches/refactor-a-durable-param-row-collapse.patch. It cannot LAND until a make pin — the nested PersistParamRow captures 21 fixed-size staging arrays and pinned predates fixed-array capture, so the pinned-seed fixedpoint goes RED and the tree would be unbuildable from the pin for every lane. Needs the pin, not more work. Was: ParseSubroutine registers a routine's params on THREE paths — external (which then Exits), forward/interface, and the body pass — and each hand-copies the ~20 durable ProcParam* columns. Measured 2026-08-30 BEFORE they were equalised: body wrote all of them, forward 14, external THREE, and that one asymmetry produced three divergences from fpc in both directions. All three copies are now complete, so no defect is open; the DUPLICATION is, and it is a standing trap because a new column added to one copy silently misses the other two. The collapse is written and blocked: the 21 staging arrays are fixed-size locals the compiler cannot capture in a nested routine, and ParseSubroutine is re-entrant so they cannot be globals. |
bug-a-a-nested-routine-cannot-capture-a-fixed-size-array |
| refactor-a-two-dyn-array-depth-functions-that-drift | A | 30 | refactor | THE MERGE IS DONE AND THIS TICKET'S SUMMARY WAS FALSE FOR THREE DAYS. DynArrayNodeDepth was DELETED on 2026-09-03 by 45391912a (fix(A): delete the second dyn-depth walker); measured 2026-09-06, it has no definition anywhere in the tree and NodeDynDepth (ast_arena.inc) is the single walker, with callers in nine files. WHAT IS LEFT IS THE RESIDUAL, AND IT IS WHY THIS ROW STAYS OPEN: two comments still name the deleted function as though it existed -- pasparser_decl.inc:1753 (which counts THREE mechanisms answering how deep is this array and is now wrong by one) and symtab.inc:15898 (past tense, deliberate history, fine as written). The decl.inc one is load-bearing prose: it is the stated justification for a design decision taken on 2026-09-06, and it was written from a count relayed by this repo's coordinator that was already three days stale. Fix the two citations and close. NOTE the third mechanism the decl.inc comment names, NodeArrNDInfo (pasparser_call.inc), is REAL and still there -- so the live count is TWO, not one and not three. |
— |
blocked (10)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| bug-b-crtl-esp-close-cannot-dispatch-socket-vs-file | B+S | 30 | bug | On ESP-IDF, close() cannot serve both file and socket fds — PalClose is fclose(ptr), PalSocketClose is lwip_close. crtl now has one close() (the file one), so socket close is wrong there | feature-pal-esp-posix-fd-semantics |
| bug-c-crtl-utoa-digit-loop-is-unbounded | C | 25 | bug | __crtl_utoa's digit loop has no bound on its index, so a wrong base turns a printf into an unbounded stack write that smashes the routine's own parameters and then walks to the guard page. Do NOT fix in isolation — it is the amplifier for an unnamed defect and bounding it would hide that. |
bug-b-reportlab-mimic-multi-font-heap-corruption |
| feature-a-every-emit-obj-object-links-its-own-full-copy-of-crtl-so-n-objects-cost-n-runtimes | A | 55→80 | feature | N pxx objects linked N copies of crtl because each object had ONE .text section, and a linker keeps or drops whole sections. BOTH HALVES OF THE SECTION ROUTE ARE BUILT, and neither changes what an object exports, so both are correct under either answer to decide-a-is-a-pxx-object-a-self-contained-runtime-or-a-translation-unit. (1) --function-sections writes one .text.<name> per function with every cross-function reference relocated; tools/function_sections_baked.py proves it from the instructions. (2) pascal26 --link garbage-collects sections from its entry the way ld --gc-sections does, and ld is its oracle for the dropped SET, the kept BYTES and the behaviour (tools/elf_reader_vs_readelf.sh stage 6). A weak crtl routine keeps one copy. WHAT REMAINS IS NOT REACHABLE BY ANY LINKER WITHOUT CHANGING THE OBJECT INTERFACE, which is why this ticket is blocked on that decide: the runtime an object keeps PRIVATE -- the Pascal builtin helpers (PXXFree, PXXRecordRelease, ...) are LOCAL symbols its own code calls, and the crtl globals its init thunk initialises live in its own .data/.bss -- is duplicated once per object that reaches it, and merging it means either exporting it (weak or COMDAT, answer A) or taking it out of the object (a runtime library, answer B). The size table with its population and compiler is in the body, 2026-09-19. | decide-a-is-a-pxx-object-a-self-contained-runtime-or-a-translation-unit |
| feature-esp-gpio-and-adc-callback-slices | B+S | 30 | feature | ESP peripheral callback API — GPIO (slice 2) and ADC (slice 3) | — |
| feature-n-import-threading-should-imply-threadsafe | N | 55 | feature | import threading compiles only with --threadsafe on the command line, so ordinary CPython source is a hard compile refusal — the wrong side of the upward-compatibility rule. The shim CANNOT declare it: the lock-implementation defines (PXX_TS_HARDLOCK on x86-64, PXX_TS_SOFTLOCK elsewhere) are applied before lexing and the lexer refuses {$threadsafe on} saying exactly that. It has to be decided at OPTION time, from a pre-scan of the source, MEASURED 2026-09-20 and the cost question is ANSWERED (chart +0.4%, ARC micro +26%, image +0.9%), so what is left is not a number but a stated intent, escalated as decide-should-a-python-program-that-imports-threading-compile-as-written; the hard part remains that the import can be in a module the main file only reaches transitively — lekkerzeilen/main.py has no threading reference at all and needs the mode because it imports app. |
decide-should-a-python-program-that-imports-threading-compile-as-written |
| feature-port-freebsd-native | A | 25→55 | feature | FreeBSD/amd64 native target — raw-syscall ELF, own syscall table, carry-flag error convention, ELF brand | feature-t-freebsd-image-and-runner |
| feature-release-checksums-repro | A | 50→80 | feature | STEPS 1-3 DONE 2026-08-31: release.sh publishes SHA256SUMS over the tarball (checkable before extracting, negative control run), and RELEASE.md + docs/install document what selfcheck.sh actually proves — with the tarball explicitly NOT claimed byte-reproducible, because gzip records an mtime. Only step 4, the minisign signature, remains, and it needs a private key no agent may generate or hold. Blocked on decide-release-signing-key-custody rather than ready, so the queue stops offering three finished steps and one impossible one. | decide-release-signing-key-custody |
| regression-lib-test-crtl-atexit-2 | C | 70 | regression | NOT ACTIONABLE AND NOT THE SLUG'S SUBJECT: crtl_atexit passes. The census step fails because it runs under $(PXX_STABLE) and the pinned compiler warns on a WEAK external. The fix (e4c72bd15) landed 18 minutes AFTER pin v410. Live compiler: 600 declared, all defined, rc=0. Clears itself at the next pin; there is nothing to fix. | — |
| regression-test-sqlite-threads-aarch64-output-mismatch-untracked-since-08-29 | A | 55 | regression | ANSWERED 2026-08-31: it is a TIMEOUT, not an output mismatch. The first full sweep carrying frankS's runner fix (fc5762a2f) says so in as many words -- FAIL aarch64 (TIMED OUT after 120s; TESTMGR_TIME_SCALE=1.00) | partial output: [] at bebac33366f5, tier full, host seven. So the job never produced a wrong answer and there is no aarch64 miscompile to chase. CAUSE, confirmed by contrast: tools/run_sqlite_thread_test.sh applies TESTMGR_TIME_SCALE (line 63) but NOT TESTMGR_LOAD_SCALE, while all three sibling qemu runners compute their budget from BOTH (t=20*s*l). Time scale was 1.00 on seven, so the budget stayed at a hardcoded 120s while the full tier ran at high concurrency. Plexus needs 37s idle and 62s under a 12-way load, so 120s under seven's sweep concurrency is simply too tight. One-line fix, in Track T's tool -- handed to T, not applied here. UNBLOCKED 2026-08-31: T applied it (ea7cb2aa2) as tsl CAPPED AT 200s, because the naive sibling formula lands on exactly 240 = the qemu class OUTER timeout, which would pre-empt the inner one and discard the very diagnostic that identified this as a timeout. Budget is now 200s under a sweep, 120s serial, unchanged. STILL OPEN because a timeout says the budget was too small and never by how much: if the next full sweep on seven still times out, the message names the cap and the known lower bound becomes 200s. That is the datum for the next move (qemu outer up, or timeouts out of RUN_RETRY_CLASSES) and it needs seven, not plexus. |
— |
| task-e-decompose-a-lekkerzeilen-roofs-frame-so-two-perf-tickets-stop-guessing-at-their-own-prize | E | 45→95 | task | BLOCKED ON AN OWNER PAUSE WITH NO DATE, 2026-09-22 -- "stop gui testing lekkerzeilen for a while please". Not blocked on a seat and not on a quiet box; do not dispatch to this until the pause lifts. Both arms are already built and verified and the bucket split is banked in the body, so resuming is cheap. ORIGINAL TEXT FOLLOWS: TWO PERF TICKETS ARE INDEPENDENTLY HELD ON THIS ONE ABSENT NUMBER AND NEITHER KNEW IT UNTIL A COORDINATOR NOTICED (2026-09-22). perf-o-the-variant-hidden-dest-clear (frankh-c0, p35) has deliberately deferred its timing half because its only cost evidence is synthetic, seven days old and measured against a binary that no longer exists; perf-a-every-return-releases-every-managed-local (frank-subcoord, p70) has independently refused to attach its measured 56.8% per-slot win to a frame, citing the size umbrella's do-not-multiply warning. Both need the same thing: NilPy method dispatch as a SHARE OF A REAL FRAME. THE MEASUREMENT HAS A HARD PRECONDITION -- the box must be QUIET, and that is a requirement rather than a preference, because the contention is DIFFERENTIAL between the arms: lekkerzeilen-7a measured the same binary, scene and pin at 530ms quiet against 624ms while peer sessions were merely COMPILING, with the CPython arm moving 66% against pxx's 18%, so a ratio taken on a loaded box is unbounded and interleaving does not rescue it. THREE THINGS THE REPORT MUST CARRY OR IT IS NOT QUOTABLE: the scene taken FROM THE INVOCATION and never from the banner (bug-e-every-world-reports-meta-name-rijn means world/roofs and world/rijn both print rijn), the worldindex rather than a tile count, and whether the box was quiet. AND THE DENOMINATOR IS THE TRAP: a variant carrier is minted PER CALL SITE, not per call (measured at HEAD, PXXDBG=a.ir, two k.m(t) sites mint two distinct unnamed carriers), so a per-slot win multiplied by call frequency mixes a per-site population with a per-call cost -- which is the umbrella's own do-not-multiply error arriving through a different subsystem. THE TWO CONSUMERS DO NOT SHARE A POPULATION and this ticket claimed they did for an hour -- retracted in the body: 8e's ~98% is tyAnsiString syms in compiler.pas (variants 3 of 23,693, 0.01%), while a NilPy program is SXR_STR 53.5%, variants 27.5%, records 16.4%, so the composition is a property of the FRONTEND and the carriers are a quarter of a NilPy program's sites rather than a share of 8e's number. |
— |
backlog (38)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| regression-fpc-bootstrap-compiler-4 | A | 40 | regression | advisory red: fpc-bootstrap#src:compiler/compiler.pas at d68ed2fe803c in step 1/1, mkdir -p /tmp/p26_fpc_canary_u && fpc -Mobjfpc -O2 -Tlinux -Px86_64 -FU/tmp/p26_fpc_canary_u -FE/tmp/p26_fpc_canary_u -… (auto-filed by twatch) |
— |
| regression-lib-test-crtl-reachability-9 | B | 70 | regression | regression: lib-test#src:tools/crtl_reachability.py at fca28056d8ec in step 84/346, stable_linux_amd64/default/pinned --mimic-fpc -dPXX_DYNLIB_LIBC -Fuexternal/synapse -Fulib/rtl -Fulib/rtl/platform/posi… (auto-filed by twatch) |
— |
| regression-lib-test-lib-classes-tthread-2 | B | 70 | regression | regression: lib-test#src:test/lib_classes_tthread.pas at 934ba04180e9 in step 1/5, stable_linux_amd64/default/pinned --threadsafe -Fulib/rtl test/lib_classes_tthread.pas /tmp/lib_classes_tthread (auto-filed by twatch) |
— |
| regression-optdiff-shard11-12 | T | 70 | regression | regression: optdiff#shard11/12 at b291b321f185 in step 1/1, tools/optdiff.sh --shard 11/12 (auto-filed by twatch) |
— |
| regression-optdiff-shard5-12 | T | 70 | regression | regression: optdiff#shard5/12 at 285208414d3f in step 1/1, tools/optdiff.sh --shard 5/12 (auto-filed by twatch) |
— |
| regression-optdiff-shard6-12 | T | 70 | regression | regression: optdiff#shard6/12 at 26db8523e829 in step 1/1, tools/optdiff.sh --shard 6/12 (auto-filed by twatch) |
— |
| regression-size-canary-size-canary-2 | A | 40 | regression | advisory red: size-canary#src:tools/size_canary.py at 2a4cd0bcf664 in step 1/1, python3 tools/size_canary.py (auto-filed by twatch) |
— |
| regression-test-aarch64-test-dynarray-to-pointer-seam-leaks | A | 70 | regression | regression: test-aarch64#src:test/test_dynarray_to_pointer_seam_leaks.pas at 4fbed6c4157e in step 5/13, tools/assert_no_leak.sh x86-64/dynarray_to_pointer_seam 50 /tmp/dtps_aarch64_x64 (auto-filed by twatch) |
— |
| regression-test-aarch64-test-loadfile-into-element-and-field | A | 70 | regression | regression: test-aarch64#src:test/test_loadfile_into_element_and_field.pas at ff2d50a2bde9 in step 2/2, tools/expect_same.sh aarch64/test_aarch64_lfef "$(tools/run_target.sh aarch64 /tmp/test_aarch64_lfef)" "$(printf 'plain… (auto-filed by twatch) |
— |
| regression-test-c-abi-mixed-link-compiler-srchash-2 | T | 70 | regression | regression: test-c-abi-mixed-link#src:tools/compiler_srchash.sh at 95fc8aff2016 in step 1/2, livesrc=$(tools/compiler_srchash.sh); \ stampsrc=$(sed -n 's/^srchash //p' compiler/.pascal26.fixedpoint); \ if [ -z "$… (auto-filed by twatch) |
— |
| regression-test-core-c-asm-in-inline-body-3 | T | 70 | regression | regression: test-core#src:test/c_asm_in_inline_body.c@2 at 4fe0e6505042 in step 7/14, python3 tools/ast_slot_overloads.py --self-check (auto-filed by twatch) |
— |
| regression-test-core-c-cross-time-and-exit-through-the-pal | T | 70 | regression | regression: test-core#src:test/c_cross_time_and_exit_through_the_pal.c at a8179a73ea84 in step 5/5, overall=0; ran=0; want=0; \ for t in i386 aarch64 arm32 riscv32; do \ want=$((want+1)); \ case $t in i386) q=qemu-i386;… (auto-filed by twatch) |
— |
| regression-test-core-test-dynarray-to-pointer-seam-leaks-2 | T | 70 | regression | regression: test-core#src:test/test_dynarray_to_pointer_seam_leaks.pas at 4fbed6c4157e in step 3/10, tools/assert_no_leak.sh dynarray_to_pointer_seam 50 /tmp/test_dtp26 (auto-filed by twatch) |
— |
| regression-test-core-test-interface-containers-2 | T | 70 | regression | regression: test-core#src:test/test_interface_containers.pas@1 at 4fbed6c4157e in step 2/18, tools/expect_same.sh test_interface_containers26 "$(/tmp/test_interface_containers26)" "$(printf 'strarr: ok\nstatic: 3… (auto-filed by twatch) |
— |
| regression-test-core-test-nilpy-qualifier-vs-cproc-2 | N | 70 | regression | regression: test-core#src:test/test_nilpy_qualifier_vs_cproc.npy at 5e9c8da7481e in step 1/3, ./compiler/pascal26 -Futest/nilpy_units test/test_nilpy_qualifier_vs_cproc.npy /tmp/test_nilpy_qual_cproc26 (auto-filed by twatch) |
— |
| regression-test-core-test-nilpy-unbound-builtin-method-2 | N | 70 | regression | regression: test-core#src:test/test_nilpy_unbound_builtin_method.npy at b4104386ae9c in step 1/13, ./compiler/pascal26 test/test_nilpy_unbound_builtin_method.npy /tmp/test_nilpy_unbndbuiltin26 (auto-filed by twatch) |
— |
| regression-test-core-test-opt-store-reload-2 | P | 70 | regression | regression: test-core#src:test/test_opt_store_reload.pas at 2c09be089d8d in step 1/28, ./compiler/pascal26 test/test_opt_store_reload.pas /tmp/test_opt_sr_O0 >/dev/null (auto-filed by twatch) |
— |
| regression-test-core-test-promoint-array-cleanup-2 | T | 70 | regression | regression: test-core#src:test/test_promoint_array_cleanup.pas at 4fbed6c4157e in step 21/41, tools/assert_no_leak.sh managed_member_array 50 /tmp/test_mma26 (auto-filed by twatch) |
— |
| regression-test-core-test-set-in-64bit-element | T | 70 | regression | regression: test-core#src:test/test_set_in_64bit_element.pas at d250db9d3678 in step 29/52, for combo in "xtensa --esp-profile=bare" "xtensa --platform=esp" "riscv32 --esp-profile=bare" "riscv32 --platform=esp";… (auto-filed by twatch) |
— |
| regression-test-debug-g-compiler-srchash-2 | A | 70 | regression | regression: test-debug-g#src:tools/compiler_srchash.sh at 7e5a0470a6b2 in step 1/2, livesrc=$(tools/compiler_srchash.sh); \ stampsrc=$(sed -n 's/^srchash //p' compiler/.pascal26.fixedpoint); \ if [ "$liv… (auto-filed by twatch) |
— |
| regression-test-emit-obj-c-obj-data-import-2 | T | 70 | regression | regression: test-emit-obj#src:test/c_obj_data_import.c at e7a805d13a09 in step 11/11, if command -v gcc >/dev/null 2>&1; then \ printf '#include <stdio.h>\nint somebody_elses_global = 99;\nint read_it(void… (auto-filed by twatch) |
— |
| regression-test-nilpy-test-cpyext-args-errors-2 | N | 70 | regression | regression: test-nilpy#src:test/test_cpyext_args_errors.npy at 523c10e42d90 in step 1/5, ./compiler/pascal26 -Futest/nilpy_units -Ilib/cpyext/include test/test_cpyext_args_errors.npy /tmp/test_cpyext_args_err… (auto-filed by twatch) |
— |
| regression-test-nilpy-test-cpyext-containers-2 | N | 70 | regression | regression: test-nilpy#src:test/test_cpyext_containers.npy at 523c10e42d90 in step 1/5, ./compiler/pascal26 -Futest/nilpy_units -Ilib/cpyext/include test/test_cpyext_containers.npy /tmp/test_cpyext_container… (auto-filed by twatch) |
— |
| regression-test-nilpy-test-cpyext-cython-2 | N | 70 | regression | regression: test-nilpy#src:test/test_cpyext_cython.npy at 523c10e42d90 in step 1/7, ./compiler/pascal26 -DPy_LIMITED_API=0x030c0000 -DCYTHON_COMPRESS_STRINGS=0 -Futest/nilpy_units -Ilib/cpyext/include te… (auto-filed by twatch) |
— |
| regression-test-nilpy-test-cpyext-errformat-2 | N | 70 | regression | regression: test-nilpy#src:test/test_cpyext_errformat.npy at 523c10e42d90 in step 1/7, ./compiler/pascal26 -Futest/nilpy_units -Ilib/cpyext/include test/test_cpyext_errformat.npy /tmp/test_cpyext_errformat26 (auto-filed by twatch) |
— |
| regression-test-nilpy-test-cpyext-hello-2 | N | 70 | regression | regression: test-nilpy#src:test/test_cpyext_hello.npy at 523c10e42d90 in step 1/4, ./compiler/pascal26 -Futest/nilpy_units -Ilib/cpyext/include test/test_cpyext_hello.npy /tmp/test_cpyext_hello26 (auto-filed by twatch) |
— |
| regression-test-nilpy-test-cpyext-markupsafe-2 | N | 70 | regression | regression: test-nilpy#src:test/test_cpyext_markupsafe.npy at 523c10e42d90 in step 1/15, ./compiler/pascal26 -Futest/nilpy_units -Ilib/cpyext/include test/test_cpyext_markupsafe.npy /tmp/test_cpyext_markupsaf… (auto-filed by twatch) |
— |
| regression-test-nilpy-test-nilpy-dotted-package-import-3 | N | 70 | regression | regression: test-nilpy#src:test/test_nilpy_dotted_package_import.npy@1 at 523c10e42d90 in step 1/8, ./compiler/pascal26 test/test_nilpy_dotted_package_import.npy /tmp/test_nilpy_dottedimport26 (auto-filed by twatch) |
— |
| regression-test-nilpy-test-nilpy-math-atan-and-atan2-bit-for-bit | T | 70 | regression | regression: test-nilpy#src:test/test_nilpy_math_atan_and_atan2_bit_for_bit.npy at b2f3e65ef050 in step 2/2, tools/expect_same.sh test_nilpy_atan226 "$(/tmp/test_nilpy_atan226)" "$(python3 test/test_nilpy_math_atan_and_atan2_bit… (auto-filed by twatch) |
— |
| regression-test-pascal-conformance-shard0-6-5 | P | 70 | regression | regression: test-pascal-conformance#shard0/6 at ef03a6282980 in step 1/1, tools/run_pascal_conformance.sh ./compiler/pascal26 library_candidates/fpc-testsuite/tests/test --shard 0/6 (auto-filed by twatch) |
— |
| regression-test-pascal-conformance-shard3-6-4 | T | 70 | regression | regression: test-pascal-conformance#shard3/6 at cc03b4a51933 in step 1/1, tools/run_pascal_conformance.sh ./compiler/pascal26 library_candidates/fpc-testsuite/tests/test --shard 3/6 (auto-filed by twatch) |
— |
| regression-test-pascal-conformance-shard4-6-5 | T | 70 | regression | regression: test-pascal-conformance#shard4/6 at d11b8a1a99dd in step 1/1, tools/run_pascal_conformance.sh ./compiler/pascal26 library_candidates/fpc-testsuite/tests/test --shard 4/6 (auto-filed by twatch) |
— |
| regression-test-pascal-conformance-shard5-6-5 | T | 70 | regression | regression: test-pascal-conformance#shard5/6 at 6e00f29b0d93 in step 1/1, tools/run_pascal_conformance.sh ./compiler/pascal26 library_candidates/fpc-testsuite/tests/test --shard 5/6 (auto-filed by twatch) |
— |
| regression-test-record-abi-mixed-link-compiler-srchash-2 | T | 70 | regression | regression: test-record-abi-mixed-link#src:tools/compiler_srchash.sh at 4c7c88d3614b in step 1/25, livesrc=$(tools/compiler_srchash.sh); \ stampsrc=$(sed -n 's/^srchash //p' compiler/.pascal26.fixedpoint); \ if [ -z "$… (auto-filed by twatch) |
— |
| regression-test-threads-test-a-threadvar-is-per-thread-2 | T | 70 | regression | regression: test-threads#src:test/test_a_threadvar_is_per_thread.pas at 6ce37dd94d7c in step 2/11, tools/expect_same.sh test_threadvar_pt26 "$(/tmp/test_threadvar_pt26)" "$(printf 'kept=4/4\nzeroed-on-entry=4/4\nno-cro… (auto-filed by twatch) |
— |
| regression-test-uforth-compiler-srchash | T | 70 | regression | regression: test-uforth#src:tools/compiler_srchash.sh@3 at 82e070429d30 in step 2/2, if [ ! -f "/home/rene/projects/uforth/uforth.py" ]; then \ echo "test-uforth: SKIP — no uforth tree at /home/rene/proje… (auto-filed by twatch) |
— |
| regression-tools-devtest-00-4 | T | 70 | regression | regression: tools-devtest#00 at fc2ce3d02553 in step 1/1, n=0; bad=0; failed=''; \ for f in tools/*devtest*.py; do \ case "$f" in *bench_timing_devtest.py) continue ;; esac; \ p… (auto-filed by twatch) |
— |
| regression-tools-devtest-sh-00-2 | T | 70 | regression | regression: tools-devtest-sh#00 at 3b5e6becd38b in step 1/1, n=0; bad=0; failed=''; \ : > /tmp/tools_devtest_sh_reds.log; \ for f in tools/*devtest*.sh; do \ case "$f" in \ *c_inte… (auto-filed by twatch) |
— |
backlog_new (0)
none
backlog-umbrella (14)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| feature-busybox-kiosk-selfhosting-target | B | 80 | feature | RE-MEASURED AT HEAD 2026-09-09 (8ea912a48, binary 049c379fd2df): x86-64 at the 394-applet scope is GREEN again -- 521 of 521 TUs become objects, they link, and the binary is byte-identical to the gcc oracle over 938 cases (tools/busybox_diff.sh --separate --targets x86_64). It was RED at 516 of 521 when this attempt started, five TUs refusing on undeclared identifier used as value -- four crtl constant gaps and one compiler bug (a call in a VLA bound), all fixed in 9b0c07c2d and 8ea912a48. The aarch64 SEPARATE build is measured BLOCKED, by the compilers own words: --emit-obj: no object writer for --target=aarch64; supported: x86-64, i386, xtensa, riscv32, which is why this umbrellas one blocked-by edge is [[feature-a-object-output-for-arm32-and-aarch64]] and nothing else -- the attempt named it, the backlog did not. aarch64 UNITY is green at HEAD at the rung-2 scope (2 applets, 28 TUs, 29 cases byte-identical); the 26-applet unity figure below was NOT re-run, and a different arbitrary 26 does not compile under gcc either, so that number stands or falls on its own applet set. TYPED umbrella 2026-09-09: this ticket called itself one in its own body while carrying type: feature and blocked-by: [], so progress.py:531 read it as a unit of work and its 80 pushed onto nothing. --- ORIGINAL: Owner-set target (2026-08-30): compile busybox, then stand up a qemu-system VM on some kernel/CPU running that busybox userland with a shell, the self-hosting pxx compiler, and a simple kiosk application. Umbrella only -- claim a rung. RUNGS 1, 2, 2b AND 3 ARE DONE. As of 2026-09-04 the userland is 258 APPLETS built busybox's own way -- 400 translation units, 400 objects, one real link, 621 cases byte-identical to the gcc oracle on x86-64 (tools/busybox_diff.sh --separate) -- and it BOOTS AS PID 1 under qemu-system-x86_64 with that same case list re-run inside the guest and compared byte for byte (tools/mkkiosk.sh --busybox= --cases=, feature-b-a-bootable-image-...). With --selfhost it reaches a SELF-HOST FIXEDPOINT INSIDE THAT VM (stage1 == stage2, seeded by pinned v403 against HEAD sources), and the kiosk app answers, so the owner's sentence -- busybox userland, shell, self-hosting compiler, kiosk app -- is met end to end on x86-64 with every one of those built by pxx. aarch64 is proven at 26 applets by unity build and still waits on an --emit-obj object writer. WHAT IS OPEN is no longer kernel-or-rootfs (settled by measurement 2026-08-30): it is aarch64, and running applets with REAL ARGUMENTS. That last is a measurement, not a ratio (feature-c-corpus-busybox-394-applets): frankc-af's 374-applet corpus -- 506 objects, 853 cases BYTE-IDENTICAL to the gcc oracle, GREEN -- was green on the same binary whose uname -a printed Linux eight times, because the corpus invokes applets with --help and --help prints a string literal. A wider, greener corpus, equally blind. The miscompile behind it (bug-c-offsetof-in-a-static-array-initializer-folds-to-zero-silently) is FIXED in 62463923f; the blindness that hid it is not, and frankD's real-argument case group (d0104ec8e) is the answer to it. |
feature-a-object-output-for-arm32-and-aarch64, feature-a-pxx-cannot-link-its-own-objects-so-a-freestanding-multi-object-program-needs-gcc |
| umbrella-a-hosted-program-is-as-small-as-it-can-be | A | 70 | umbrella | Owner-set target 2026-09-18, in two directives: "mark as read-only where possible" and "strip code and associated data where possible." This is the PC/hosted half; the ESP half is [[umbrella-an-esp32-image-is-as-small-as-it-can-be]] and the two share most blockers. MEASURED FLOOR AT HEAD (2026-09-18, x86-64, WriteLn('hello'), verified to run): code 195 B, data 336 B, bss 41,800 B. So the code half is already excellent and the DEFAULT is not: the same program at default flags is 67,104 B of code, 4,328 B of data and the same 41,800 B of bss. Three facts frame every rung. (1) code= is PAGE-QUANTISED, so nothing here is gradeable until that is fixed — it is the first blocker for a reason. (2) --dce removes 44.7% of code and ZERO bytes of data or bss, so the size problem and the RAM problem are different problems with different passes. (3) 78% of the bss floor is one constant, SIG_ALTSTACK_SIZE = 32768, reserved even under --no-signals. |
bug-a-a-frontend-cannot-see-that-a-backend-calls-library-routines-it-never-mentions, bug-a-a-pascal-hello-world-is-63kb-after-emission-size-dce, bug-a-a-typed-const-record-is-built-by-startup-code-not-stored-as-data, bug-a-the-signal-alt-stack-is-32768-bytes-of-unconditional-bss, bug-t-code-is-page-quantised-so-there-is-no-instrument-for-size-work, feature-a-an-extern-only-variable-still-reserves-its-storage, feature-a-the-threadvar-area-is-3072-bytes-of-bss-in-every-program-that-has-no-threadvar, feature-a-there-is-no-read-only-load-segment-so-nothing-can-be-flash-resident, feature-a-unreferenced-class-rtti-keeps-every-method-alive, feature-opt-rtti-emit-on-use |
| umbrella-a-stranger-can-get-a-working-compiler-from-a-release | T | 80 | umbrella | GOAL, not a unit of work. Owner, 2026-09-06: 'project goal, let's slowly prepare for a release.' The target is not a tag and not a document -- it is a person who has never seen this repo getting a compiler that works, from an artefact they can verify. SLOWLY is part of the instruction: this ranks steadily in the background, it does not displace development. Attach whatever an ATTEMPT breaks on; do not pre-populate it from the backlog by guessing. | bug-t-native-s-red-is-one-row-and-full-s-is-ninety-four-so-they-are-different-problems, bug-t-the-documented-build-path-never-enumerates-what-it-needs, decide-release-signing-key-custody, feature-release-checksums-repro, umbrella-one-full-tier-run-with-no-red-tier |
| umbrella-an-esp32-image-is-as-small-as-it-can-be | A+S | 70 | umbrella | Owner-set target 2026-09-18: same goal as [[umbrella-a-hosted-program-is-as-small-as-it-can-be]], on ESP, where it is the difference between running and not. RUNG 0 IS ANSWERED (2026-09-18): IDF costs 69,476 B of a C3's 409,600 and leaves 340,124 bytes of free heap for a non-networking app, ~285,100 projected with WiFi linked — measured here with IDF v6.0.1 under the Espressif qemu, no chip needed. So the budget IS a number, and our NilPy hello-world's 146,612 B of data+bss is 43% of it. The runtime WiFi-buffer term he named still needs a chip (qemu has no radio model). Four facts frame it. (1) --esp-profile=bare loads code+data+bss into IRAM at $40380000 with a 256 KiB region, because qemu's esp32c3 machine models it as one RWX region (defs.inc:2275) — that is a QEMU shape, not a chip shape; the DEFAULT IDF profile keeps .text in flash. (2) SUPERSEDED 2026-09-20 -- --dce runs on every target but wasm32 now and BOTH ESP demos boot with it: C3 image 3,326,224 -> 2,307,648 B (-31%), S3 3,246,288 -> 1,996,848 B (-38%). That is a flash win and still not a RAM win, and neither reaches the stock 1 MB partition, which needs -66%. What the remaining 2 MB IS, measured per unit: pylib 53.6% / pyeval 30.1% on riscv32 (47.5% / 39.3% on xtensa) -- a runtime eval() tree-walker whose own header says it is NOT auto-used by NilPy is the second largest component of a program that never calls eval. (3) There is NO .rodata anywhere in the compiler — grep -c rodata is 0 in elfwriter.inc and defs.inc — so no constant can be flash-resident by construction. (4) The escape that would shrink an ESP image, -uPXX_MANAGED_STRING, SILENTLY EMITS AN EMPTY IMAGE on the bare path and reports ok:. That is the urgent one. THERE IS AN SRAM INSTRUMENT SINCE 2026-09-20 (examples/esp32/nilpy-c3/build.sh sram: our object's data/bss plus the free DRAM pool read off the chip's own heap_init, positive-controlled by a 40,000-byte static array that moves the pool by exactly 40,000), and it settles the owner's SRAM ruling for the whole code-removal family: a rung that removes CODE pays SRAM only where it drops the LAST RELOCATION to an SDK component, because .text is flash-mapped and pxx's DCE drops bodies, not globals. Measured on the NilPy print demo, --dce cuts 30.8% of riscv32's code for +1,840 B of pool and 71.0% of xtensa's for +2,112 B — under 1% either way, all of it lwIP buffers that --gc-sections collects once the socket relocations go. So rank DATA rungs above code rungs on this umbrella: our own data+bss WAS 125,832 B (.data 36,480 + .bss 89,352), 59.6% of the then-211,296 B free pool, and --dce does not touch one byte of it. FIRST DATA RUNG LANDED 2026-09-20 and it was the largest single one available: 60,296 B now (.bss 89,352 -> 23,816), the free pool 276,832 B, both instruments moving by exactly 65,536 -- the NilPy heap arena was reserved on a profile where nothing could read it, because under IDF PXXAlloc IS calloc ([[bug-a-the-nilpy-heap-arena-is-64-kib-of-dead-sram-on-the-esp-idf-profile]], proved by reachability and confirmed by both demos booting). Our share of the pool is now 21.8%, so the next data rung is worth proportionally less and the REMAINING list below is what is left. Take that figure from the SECTION TABLE, never from the compiler's ok: line -- data= there is the whole data segment including .rodata, which has no W flag and which IDF flash-places, so the ok: line overstates ESP SRAM by the entire read-only pool (178,008 against 125,832 here, 42%); this summary carried the wrong number for an hour and the instrument's own positive control could not catch it, because a static array lands in .bss and .bss was never the broken readout. The rung that moves it is the REMAINING list of [[feature-a-there-is-no-read-only-load-segment-so-nothing-can-be-flash-resident]] -- NilPy VMTs, prop/method arrays, IMTs, dispatch tables, float constants -- each of which was deferred pending a measurement this instrument is the first thing able to take. (Its ESP-IDF half ALREADY LANDED 2026-09-18, so the 178,008 B is downstream of that win, and the signal-alt-stack ticket is p15 with its SRAM case discharged 2026-09-19: an earlier version of this summary named both as upcoming and was wrong about each.) |
bug-a-a-pascal-hello-world-is-63kb-after-emission-size-dce, bug-a-a-static-nilpy-program-links-the-runtime-eval-interpreter, bug-a-dce-refuses-every-target-except-x86-64, bug-a-emit-obj-retains-pxxassert-so-one-ansistring-in-it-imports-the-whole-esp-pal, bug-a-riscv32-dce-keeps-135-more-bodies-than-xtensa-on-one-program, bug-a-the-esp32-bare-image-doubled-in-code-and-grew-half-again-in-bss, bug-a-the-heap-arena-reserves-256-mib-without-map-noreserve-so-a-small-guest-cannot-run-any-allocating-pxx-program, bug-a-uPXX_MANAGED_STRING-on-esp-bare-emits-an-empty-image-and-says-ok, bug-t-code-is-page-quantised-so-there-is-no-instrument-for-size-work, bug-t-the-esp-bare-suite-is-in-no-tier-so-nothing-ever-runs-it, feature-a-there-is-no-read-only-load-segment-so-nothing-can-be-flash-resident, feature-a-unreferenced-class-rtti-keeps-every-method-alive |
| umbrella-compile-and-run-dosbox | C | 50 | umbrella | GOAL, not a unit of work. The flagship real-program proof: a large real C/C++ codebase that either builds and runs or does not, with no partial credit to award ourselves. Owner named it first when stating the goal. Attach whatever the ATTEMPT breaks on -- do not pre-populate this from the backlog by guessing. | bug-a-an-object-neither-exports-nor-imports-data-symbols-and-links-silently-wrong, feature-c-corpus-busybox-multi-applet |
| umbrella-cross-target-codegen-is-correct | A | 80 | umbrella | GOAL, not a unit of work. The owner's ranking: 'cross platform has way prio above look-if-I-do-this-on-platform-that-it-would-break-z'. A program that compiles right on one target and wrong on another is the defect this umbrella exists for; a hypothetical about an untried platform is not. Measured target clusters: xtensa 11, riscv 8, arm32 5, i386. | bug-a-hosted-xtensa-diverges-from-the-oracle-on-21-cross-programs, bug-a-i386-c-main-gets-argc-and-argv-swapped, feature-a-a-stackful-coroutine-is-four-targets-only-so-examples-net-httpdemo-cannot-cross, feature-a-port-alloca-to-i386-arm32-and-riscv32, refactor-a-the-scope-exit-managed-local-release-loop-has-seven-copies |
| umbrella-lekkerzeilen-compiles-and-runs-under-nilpy | N | 90 | umbrella | 2026-09-13, LATEST — THE WHOLE SIMULATOR NOW BUILDS AND THE WALL IS A SHADER THAT NEVER REACHED THE DRIVER. --shot gets through App.__init__ entire -- the world, the environment, the camera, the traffic, and vessel.build with its 22-parameter Vessel(...) -- and dies at gfx.Shader: ShaderError: hull failed to link: (0) : error C5145: must write to gl_Position. THAT MESSAGE IS NOT ABOUT THE SHADER. The source we hold is 390 bytes and is BYTE-IDENTICAL to CPython's (dumped from both and compared), and it writes gl_Position on its last line. Asked of the DRIVER with two instruments that fail differently -- glGetShaderiv(GL_SHADER_SOURCE_LENGTH) answers 1, glGetShaderSource hands back 0 characters -- so the driver compiled the EMPTY STRING, which compiles fine and then cannot link. Cause measured from the callee's side, the same way the bytearray case was settled: glShaderSource(shader, 1, [encoded], None) passes a one-element LIST to a const GLchar *const *, and a list bound to a C pointer parameter passes the TPyList OBJECT POINTER -- write(1, lst, 24) emits the VMT word, then the count 1, then the capacity 8, then the heap pointer, field for field. It is the direct sibling of today's bytearray fix ONE LEVEL UP, and it is NOT the same fix: that one answers where this object's DATA is with a word the object already holds, where a char ** wants an array of N pointers that does not exist in the heap at all and has to be built with a lifetime. Filed at 80 as bug-n-a-list-bound-to-a-c-pointer-to-pointer-parameter-passes-the-object-pointer, with the three options and the lekkerzeilen-side fork both written down. GETTING HERE TOOK TWO FIXES TODAY BEYOND THE TWO ABOVE. The with-header star-unpack nil reference (--shot used to die before open_window was entered). And then a METHOD'S NON-CONSTANT DEFAULT read as None when the call is inside the class -- Lines._rows(count=STATIONS) called as self._rows() from the draft property arrived with count=None, i / count raised TypeError: expected a number, got NoneType, and that one TypeError was the whole of both walls: the interactive --open-water path and --shot were failing on the SAME defect, not two. Located by marking every statement of app.py / vessel.py / math3d.py / lines.py with its own line number and reading the last one printed, because an unhandled exception names no location -- five rounds, and three of them corrected a wrong guess (it was NOT Vec3, NOT the 22-keyword Vessel call, NOT the min/max-over-a-genexp properties; each of those was probed and cleared). --m0 MEANWHILE RENDERS CONTINUOUSLY: window, GL 3.3, drawable : 1280x720, then its render loop spinning for 40+ seconds without raising, gl.clear_color / gl.clear / win.swap every iteration. FRAMES PRESENTED IS STILL NOT ESTABLISHED, and the reason is now mundane: the count prints only on a CLEAN exit, the session is Wayland so xdotool cannot reach the surface, and SIGTERM killed the process rather than arriving as SDL_QUIT. --shot is the instrument that answers it, so it is the one to fix. — PREVIOUS ENTRY — 2026-09-13, THE DEMO NOW RENDERS CONTINUOUSLY AND ITS CAPTURE PATH REACHES THE SCREENSHOT CALL. --m0 opens a window, brings up GL 3.3, prints the driver strings and its own drawable : 1280x720, and then spins its render loop for 40+ seconds without raising -- gl.clear_color / gl.clear / win.swap every iteration, which is the first time any loop in this program has survived more than one frame. At 08:00 the same command died on gl.clear() immediately. FRAMES PRESENTED IS STILL NOT ESTABLISHED and the reason is now mundane rather than interesting: the count prints only on a CLEAN exit, the session is Wayland so xdotool cannot reach the surface at all, and SIGTERM killed the process rather than arriving as SDL_QUIT. That question is properly answered by --shot, which is non-interactive and writes a PNG, so it is the instrument to fix rather than a window to poke. --shot ITSELF MOVED TODAY: it used to die with Runtime error 216 (nil reference) before open_window was even entered, from a star-unpack in a with HEADER reading its own hidden temp before it was filled (with platform.open_window(\"lekkerzeilen — capture\", *size) as window:, app.py:4236). A star expansion lowers to an arity dispatch over a hidden tyClass temp whose setup is hoisted to STATEMENT level, and with is the one construct that builds its OWN sequence and puts the manager evaluation inside it, so the flush landed after the assignment that reads the temp; if/while/for/try/plain-statement headers were each measured correct, so with was alone. Filed and fixed as bug-n-a-star-unpack-in-a-with-header-reads-its-own-temp-before-it-is-filled. THE NEW WALL, AND IT IS THE SAME ONE ON BOTH PATHS: TypeError: expected a number, got NoneType. --shot and interactive --open-water now fail identically, which makes it ONE defect and not the two this summary previously implied. It is located by SYMPTOM ONLY so far -- an unhandled exception names no line, the message is assembled by PyTypeError(t, want) in pylib.pas with want='a number' from the numeric coercion helpers, and gdb cannot help because it runs this binary fine on the windowless --probe path and stalls indefinitely on any windowed run, including under SDL_VIDEODRIVER=offscreen. — PREVIOUS ENTRY — 2026-09-13, A FRAME HAS BEEN DRAWN AND ITS PIXELS READ BACK. Measured with a STANDALONE probe that names no lekkerzeilen module -- so nothing the demo already makes work could supply what it needs -- going straight at SDL2 and GL: glGenBuffers(1, buf) answers name 1 through a bytearray out-parameter, glClearColor(0.2, 0.4, 0.6) + glClear + glReadPixels(0, 0, 2, 2, GL_RGB, GL_UNSIGNED_BYTE, px) returns [51, 102, 153] for all four pixels, and the swap completes. The expected value was chosen so no channel is 0 or 255 and the three differ, because a blank buffer, a saturated one and a channel swap must all be distinguishable from the right answer -- and the FIRST run of that probe did come back twelve zeros, from reading AFTER SDL_GL_SwapWindow where the back buffer is undefined, which is also exactly what an unwritten buffer looks like and would have been filed as a glReadPixels defect. SCOPE THIS EXACTLY: a clear to a known colour, read back and verified. NOT a triangle, NOT a shader, NOT a VAO bound and drawn, and NOT the simulator; task-b-write-the-lekkerzeilen-pxx-platform-backend stays open. What it does retire is the clause in the bytearray ticket saying nothing can be drawn BECAUSE an object cannot be created: creation works. The DEMO itself now gets further than any claim here covers -- --m0 prints drawable : 1280x720 and reaches its render loop without raising on gl.clear() -- but its frame count prints only on a clean exit, and closing the window from outside failed (Wayland session; forcing X11 and driving it with xdotool killed the process through its X connection rather than through the QUIT path, my own interference), so FRAMES PRESENTED BY THE DEMO is not established. Two fixes got it here, both today: a bytearray bound to a C pointer parameter (two arms -- static spelling in IRLowerCallArg, dynamic spelling in PyCoerceCallableArgsIn) and shape (a) of the class-as-value dispatch (a CLASSREF arm, for a name one class carries at class level and others carry as an instance method -- gl.clear). — PREVIOUS ENTRY — 2026-09-13, THE WINDOW NOW REPORTS ITS OWN SIZE: --m0 prints drawable : 1280x720, which is SDL_GL_GetDrawableSize(h, self._wbuf, self._hbuf) writing into two four-byte bytearrays and _i32() unpacking them. That was the previous wall and it is cleared: a bytes/bytearray handed to a C routine's POINTER parameter used to pass the TPyBytes OBJECT POINTER, so a reader saw the VMT and a writer destroyed it. The fix needed TWO arms, not one, and which half you hit depends on the SPELLING: a NAME or a FIELD is statically a TPyBytes and diverts in IRLowerCallArg; a PARAMETER or a call result arrives as a tyVariant that the frontend has already rewritten, and diverts in PyCoerceCallableArgsIn. The parameter spelling is the one this program uses everywhere -- _i32(buf, off) and every gl wrapper take the buffer as a parameter and hand it on -- so the first landing, which had only the static arm, fixed the reproducer and not the demo. THE NEW WALL, and it is a ticket already open: gl.clear() raises AttributeError: 'type' object has no attribute 'clear'. clear is a @staticmethod on the gl namespace class and ALSO an ordinary method on three other classes in the program, and PyClassLevelOnlyMeth admits a class-as-value receiver only for a name with no instance carrier at all. That is shape (a) of bug-n-a-class-level-method-through-a-class-value-is-refused-when-the-name-has-two-carriers, raised 40 -> 80 today on exactly this measurement -- and its own "the platform backend does not need this" sentence is marked false where it stands. STILL NOT ESTABLISHED: nothing has been DRAWN. No glClear, no swap, no frame; every glGen* returns its new name through a buffer and those now work, but no program has reached one. A driver that answers a string query and a window that knows its size is not the simulator running. — PREVIOUS ENTRY — 2026-09-13, A GL CALL HAS NOW SUCCEEDED AND THE DRIVER ANSWERED. --m0 opens a window, brings up a real context, and prints three lines that can only come from the hardware: backend : pxx, renderer : NVIDIA GeForce GTX 1660 SUPER/PCIe/SSE2, gl : 3.3.0 NVIDIA 580.178.04. Those strings are glGetString(GL_RENDERER) and glGetString(GL_VERSION) round-tripping through gl.get_string, which is the very call this ticket named as the wall an hour earlier, so the sentence further down saying NO GL CALL HAS SUCCEEDED is superseded and marked where it stands. The defect behind it is fixed: a class-level method (@staticmethod or @classmethod) reached through a class held as a VALUE -- gl = _backend.gl then gl.get_string(...). CAUSE, and it was not the one the ticket predicted: PyParseVariantMethod's receiver guard is HOISTED ahead of every dispatch arm and asserts pyvar_is_objtag, so a receiver holding a class (VT_CLASSREF, tag 11) was refused before any arm could run. Both decorators ride UMthIsStatic and BOTH take the class at slot 0 -- injected $clsrecv for a static, declared cls for a classmethod -- so the classref payload, the RTTI blob, is exactly what slot 0 wants and the call needs no instance at all. The ticket had argued AGAINST this route on the premise that a staticmethod has no self; that premise was read off the NAME UMthIsStatic and is false (UMthNoSelf is the one that means no Self), and it is marked superseded in the ticket rather than deleted. A LANDED FIXTURE, NOT A PROBE: test_nilpy_a_class_held_as_a_value_reaches_a_class_level_method.npy, eleven rows, whose control is that rows A-E all raise AttributeError: 'type' object has no attribute <name> under pin v408 and pass at HEAD -- the five rows that moved are exactly the five the fix is about -- and whose last two rows assert what is STILL REFUSED so the limit lives in the suite and not only in a ticket (a name carried both at class level and as an instance method, and two distinct class-level carriers; both want one unbuilt mechanism, a CLASSREF arm in the runtime arm chain, filed as bug-n-a-class-level-method-through-a-class-value-is-refused-when-the-name-has-two-carriers). THE NEW WALL IS ONE LINE FURTHER ON AND IT IS A BIGGER ONE: win.size segfaults, and the cause is that a bytearray bound to a C POINTER PARAMETER passes the TPyBytes OBJECT POINTER rather than the data pointer, so the callee reads and writes the instance's own header. Read from the callee's own side -- a 40-byte bytearray of 0xAA handed to write(1, b, 24) emitted three words that are TPyBytes field for field: the VMT pointer, then FLen=40 with its padding, then FData, the heap pointer the callee actually wanted, 16 bytes into what it was given. So every C function that READS one sees a VMT pointer and a length where it expected bytes, and every one that WRITES destroys the VMT: pipe(b) put fd 3 and fd 4 over it and the next dispatch jumped through 0x400000003. MY FIRST READING OF THOSE BYTES WAS WRONG AND IS RECORDED AS WRONG IN THE TICKET: I called them a two-word {data pointer, length} descriptor, which explains words one and two and waves at word three. Two cheap checks separate the readings and I had both before writing the wrong one -- the second word was 16 on the first run and 16 is also SizeOf(Variant), and the FIRST word did not move between a 16-byte and a 40-byte bytearray, which a data pointer does and a VMT pointer does not. The whole pxx backend is built on that shape -- SDL_PollEvent(self._event_buf), SDL_GL_GetDrawableSize(h, self._wbuf, self._hbuf), glGetShaderiv(shader, pname, buf) -- so it is wired above at prio 80 as bug-n-a-bytearray-bound-to-a-c-pointer-parameter-passes-the-object-pointer-not-the-data. IT HIDES FROM THE OBVIOUS PROBE, which is why a whole backend was written on it: len(b) and list(b) at module level right after the call all answer correctly, because that path does not follow the clobbered word, and passing the same b to any function crashes. A probe that re-reads the buffer in place reports the call as working. AND NOTHING CAN BE DRAWN UNTIL THAT ONE COERCION MOVES, WHICH IS A STRONGER STATEMENT THAN THE WALL BEING NEXT. Counted in _pxx.py: 20 call sites hand a bytearray to a GL or SDL function, and they are not twenty separate features -- every glGen* entry point returns its new name THROUGH a buffer, so a VAO, a texture, a framebuffer and a buffer object are all uncreatable; glGetShaderiv and the two InfoLog calls mean a shader that fails to compile cannot even say why; SDL_PollEvent is the whole event loop; glReadPixels is capture; SDL_OpenAudioDevice is audio. Read that as REACHABILITY and not as effort: it is one fix, and CLAUDE.md's four-walls lesson says a count of units blocked is not a count of work -- one shared coercion appearing twenty times is precisely the histogram that lesson is about. STILL NOT ESTABLISHED: nothing has been DRAWN -- no glClear, no swap, no frame -- and task-b-write-the-lekkerzeilen-pxx-platform-backend stays open. A driver answering a string query is not the simulator running. === earlier the same day === FOUR OF THE APP'S NON-WINDOW MODES NOW RUN AND --starts IS BYTE-IDENTICAL TO CPython. Every differing line is gone: the last one was the getattr defect named further down, and it is FIXED, not parked. --help is byte-identical to CPython, --conform passes its own self-check with CPython-identical pixels digests on all six rows, --probe differs only in the line that names the backend (pxx against ctypes, which is the truth), and --starts — which used to print open water: nowhere in particular and exit 0 — now lists the rijn region and both places to start, BYTE-IDENTICAL to CPython with no remaining differences (this sentence said APART FROM ONE LINE until the getattr fix landed the same evening). Two compiler defects stood between those two states, both found by RUNNING the app and neither visible to any compile census, and both are the same shape: ONE OWNER OF A RULE THAT TWO OTHER OWNERS ALREADY HAD. (1) PyInferExprType had no arm for the None LITERAL, so it answered tyUnknown — the JOIN'S IDENTITY ELEMENT — and a None arm of a conditional expression VANISHED from a def's inferred return type. self.y = None if y is None else float(y) registered a Double result and raised TypeError: expected a number, got NoneType from inside world.Furniture.__init__, which writes that shape eleven times; the int arm does not raise and silently answered 0. PyMakeNone already tagged its node tyVariant and the return scan's own sawNone already forced a variant for a bare return None. (2) A @property was unreachable through a dynamically-typed receiver whenever ANY class in the program carried that name as a plain FIELD — a real FIELD of that name anywhere wins, and anywhere was the whole program — so the getter was never called and the read took an offset belonging to another class. World.name is self.meta.get(name, world) behind @property while Route, Furniture, Pound and Region each carry a plain name field, so region.name answered '' and the app named no world at all, which in turn made the remembered session position look like another world's. A METHOD of one name on two unrelated classes and a property no class fields were ALREADY correct, which is exactly why the MIXED population survived both of those being right. Landed as 152b316c1, gate quick GREEN, two fixtures each verified to FAIL on pin v408 — rc=217 and a segfault — and pass at HEAD. MY OWN PUBLISHED GUESS WAS WRONG AND IS WITHDRAWN: this summary and a peer both carried Furniture(**dict(zip(columns, row))) as the next wall, called it the unfinished arm of an earlier keyword fix, and flagged it as unmeasured. Measured, all seven **-unpack spellings match CPython, including 13 keyword arguments with six None values into a constructor with __slots__. Naming a wall is not measuring one. THE ONE REMAINING --starts LINE is getattr/hasattr disagreeing: getattr(obj, \"m\", None) with a LITERAL name on a statically class-typed receiver silently returns the DEFAULT for a plain method, so _has_ground's getattr(region, \"key_at\", None) answered None and the containment lambda answered False for every position. NOW FIXED — bug-n-getattr-cannot-see-a-method-and-segfaults-through-a-dynamic-receiver, a 4-day-old ticket re-measured rather than inherited (both repros still reproduced; its variant-receiver diagnosis did NOT). TWO defects, and the SECOND is the root cause that the first attempt — attempted, REVERTED, and parked in the ticket — could not find. (a) The literal-name path asked only 'is it a declared FIELD' and 'is it a PROPERTY'; a plain method is neither, and the wider predicate that DOES see methods was consulted only by hasattr, which is the single clause that made the two spellings disagree. Now the literal name goes to the same runtime resolver the COMPUTED spelling already used, so the two are one mechanism. (b) THE ROOT CAUSE: PyMethodUsedAsValue had no arm for a LITERAL getattr, so the bound-method pair the resolver hands back was never normalised to the function-object ABI and returned its result in a register while the caller expected the hidden-destination convention. THAT is why the parked attempt entered the method with the right receiver and arity and then answered '' for a string and SEGFAULTED for an int — the resolver was never the defect, the ABI of the thing it hands back was, and all four hypotheses ruled out in that ticket's dead-end section were about REACHING the method. The parked patch is now EXPLAINED rather than merely superseded. AND THE FIXTURE'S DESIGN IS PART OF THE FIX: it contains no computed getattr, because PyModuleHasComputedGetattr is module-wide and coarse — one computed getattr anywhere normalises EVERY method and every literal row then passes on the unfixed compiler. Measured, not feared: the probe this fix was first verified with carried a computed-name REGRESSION row, reported all seven rows healthy, and the defect was still there. A WINDOW NOW OPENS UNDER pxx, AND THAT SENTENCE USED TO SAY NOTHING HAD BEEN EXERCISED. Measured 2026-09-13: --m0, the app's own seam test (a window, a context, a loop, a clean exit), gets through platform.open_window(...) and win.set_vsync(True) and prints backend : pxx, then dies on the NEXT line at gl.get_string(gl.RENDERER) with AttributeError: 'type' object has no attribute 'get_string'. CONTROL, on the same box and the same display: CPython runs --m0 to completion -- NVIDIA GL 3.3, 1280x720, 345 frames in 5.8s at 60fps -- so the failure is ours and not the environment's. (The first control run looked like a HANG and was my own instrument: | head -8 swallowed the output when the timeout's SIGTERM killed the pipeline. Redirecting to a file showed a healthy 60fps window all along.) The wall is ONE defect, wired above as bug-n-a-staticmethod-or-classmethod-is-unreachable-through-a-class-held-as-a-value [CLEARED 2026-09-13, same evening -- see the top of this summary; the wall is now one line further on, at win.size]: _pxx.py declares class gl: as a namespace of @staticmethods and platform/__init__.py re-exports it as gl = _backend.gl, and a no-instance method is unreachable through a class held as a VALUE while class attributes and instance methods both survive the same binding. STILL NOT ESTABLISHED: nothing has been DRAWN, nothing has been DRAWN half still holds.], and task-b-write-the-lekkerzeilen-pxx-platform-backend stays open -- a window opening is not the simulator running, and the honest claim is that the seam is further along than this ticket knew rather than that the graphics path works. What IS now false is the old claim that ctypes decides whether the app proper runs: the pxx backend opens the window itself. Also unchanged: the __file__-in-a-package fork is the owner's and is not fixed. AND THE --conform SIZE CLAIM IN THE 2026-09-12 SUMMARY BELOW IS NOW STALE: it says our STORED-only deflate makes the PNGs 3-12x CPython's, which was true and is not any more. A peer landed a real encoder (3c39dbf62, LZ77 + fixed Huffman) and lib/rtl is read LIVE at compile time, so no pin sits between it and a lekkerzeilen build — one rebuild carries it. Measured on the same six cases, and then RE-measured after 10819e44d added DYNAMIC Huffman: total 12398 (stored-only) -> 4183 (fixed) -> 3663 (dynamic) bytes against CPython's 3684, i.e. 3.37x -> 1.135x -> 0.994x. WE ARE NOW SMALLER THAN CPython's zlib 1.3.1 on this corpus. The three rows that were the residual are all clear: 3x129 1.244x -> 1.013x, 257x2 1.130x -> 0.979x, 37x11 1.151x -> 1.000x, and 64x40 11.37x -> 1.000x. THREE OF THE SIX ARE EXACT SIZE TIES (685=685, 69=69, 881=881) AND THAT IS NOT WHAT TWO INDEPENDENT NEAR-OPTIMAL ENCODERS NORMALLY LOOK LIKE -- they land close, not equal. frankb-7e's HYPOTHESIS, recorded as a hypothesis because neither of us has run the test: on those inputs our match finder produced the same token stream as zlib's and the Huffman code LENGTHS came out the same (length assignment is essentially determined by the symbol distribution), so only the tie-breaking inside tree construction differs, permuting which code VALUE a symbol gets without changing any length -- same tokens, same lengths, different bytes, identical size. THE TEST IS NAMED AND CHEAP AND WAS NOT RUN: the code lengths are recoverable from a dynamic block's header, so two equal-size streams whose headers decode to the same lengths would confirm it. DO NOT read three exact ties as evidence of encoder parity until someone does -- that is the reading this sentence exists to block, and an earlier draft of it invited exactly that reading. pixels stays 6/6 identical to CPython across all three encoders, which is the claim that matters; one file digest now matches byte-for-byte and that is the 1x1 case, 69 bytes of one pixel, where agreement is close to forced -- it is NOT encoder parity and must not be quoted as it. The size objection in this ticket is therefore GONE rather than reduced, and this paragraph has now been wrong in both directions, which is the point of the instrument lesson below. THE INSTRUMENT LESSON IS THE REUSABLE PART and it is the peer's: 6/6 file digests differ is the correct result AND it is exactly what a build with no encoder in it produces, so that column has one bit of output with both values legal and can never say whether a fix is in the build. The quantity that carries information is the SIZE RATIO. The pixels column remains the correctness claim — 6/6 identical to CPython, re-verified — and a differing file column remains not a fault, which is the app's own policy and not a measurement. The 2026-09-12 summary below is accurate for its own date EXCEPT for that size figure, and is kept. === 2026-09-12, superseded === IT COMPILES AND IT RUNS. pascal26 --threadsafe -dSDL_DISABLE_IMMINTRIN_H -dGL_GLEXT_PROTOTYPES lekkerzeilen/__main__.py exits 0 with ZERO errors and 112 warnings, producing an 11431860-byte ELF of 11657 procs; the binary runs, --help exits 0, and its output is BYTE-IDENTICAL to CPython's. Disk checked on BOTH axes before believing any of it (1% bytes, 1% inodes) because an ENOSPC write makes this compiler print ok: with exact byte counts for a truncated binary. AND --conform NOW PASSES, which is the app's OWN self-check and a better instrument than anything in this ticket: it encodes six PNGs through capture.write_png and decodes them back through png.decode, so it exercises zlib, struct, bytes handling and file I/O and then checks its own answer — it carries its own oracle rather than needing a known-good output kept in step beside it. All six round-trip and the pixels digest, which the app's own docstring says must match on any correct runtime, is IDENTICAL to CPython on all six rows. The bytes and file columns differ and that is not a fault: our zlib is RFC 1950/1951 from scratch, reports pxx-rtl, and deliberately emits STORED deflate blocks — lib/rtl/zlib.pas says so in capitals and names this very program, so the PNGs are 3-12x larger and every byte of them is correct. [SUPERSEDED 2026-09-13, and it was THIS sentence the note at the top of this summary is about: 3c39dbf62 gave zlib real LZ77 + fixed Huffman and 10819e44d added DYNAMIC Huffman, so it no longer emits stored-only and the measured total is 0.994x CPython -- SMALLER than CPython -- not 3-12x. (This marker itself said 1.14x for about five minutes, which was the fixed-Huffman figure, stale before it was pushed: I wrote it from the measurement I had rather than re-running against the tree I was committing to.) Both halves of the sentence are now false. Marked rather than deleted because it is the worked example: I prepended a correction at the top of this summary and left this sentence beneath it, where it kept inheriting the freshness of the digests beside it.] Getting there took one more wall, found only by running it: bytearray(x) over a dynamically-typed x raised TypeError: expected a number, got object, because bytes grew a Variant overload when an Integer one was added beneath it and bytearray — which has had the Integer arm all along — never did. png._unfilter writes bytearray(raw[position:position + stride]) with raw a parameter, and an intermediate local rescues it, which is why no ordinary spelling of it was broken. THREE WALLS CLEARED AND ONLY THE MIDDLE ONE WAS ON THE BOARD, which is the point of attempting the target rather than triaging: (1) io.open did not exist — lib/rtl/io.pas carried BytesIO and StringIO only, and world.py:697 opens a file with io.open(path, encoding=utf-8); it is now a unit-level function delegating to the bare builtin's pyfile_open, claiming nothing the builtin does not already do and REFUSING every encoding/errors/newline value it cannot honour rather than ignoring them. (2) __doc__, the banked ticket — and it needed an arm in BOTH identifier factors (pyparser.inc carries NilPy's, pasparser_expr.inc carries Pascal's, and the first patch went into the one a .npy never reaches), plus CPython 3.13's compile-time docstring DEDENT, which the ticket did not predict and which this app's --help output depends on character for character — whitespace-only lines are IGNORED in the minimum and a partially-stripped tab is re-emitted as SPACES, neither of which a first reading of the rule gives. (3) 0.0 ** <fractional> raised ValueError: math domain error where CPython answers 0.0 — a missing row in pypow_cx, which was written from pypow_v and copied only its negative-exponent refusal, so with no PyPowHook installed the next line was exp(e * ln(0.0)). lines.py builds its hull tables at MODULE level and station(0.0) computes math.sin(0.0) ** fine, so the first station of the first hull killed the program before a single line printed. THE LESSON IS ABOUT THE INSTRUMENT, NOT ABOUT **: the moment the closure compiled, every wall left was a RUNTIME one, and a first-failure compile census cannot see those at all — it had nothing to say about the bug that stopped the program. Run the thing. WHAT IS NOT ESTABLISHED, AND DO NOT READ THIS SUMMARY AS SAYING THE SIMULATOR RUNS: only --help has been exercised. No graphics path, no --starts/--probe/--conform, nothing that opens a window. task-b-write-the-lekkerzeilen-pxx-platform-backend is still open and ctypes still decides whether the app proper runs — that is unchanged by tonight. --starts IS A WALL AND IT IS A SILENT ONE — under pxx it prints open water: nowhere in particular, which is the point and exits 0, where CPython lists the rijn region and two places to start. Cause measured: __file__ for a module inside a PACKAGE collapses the package directory, so dirname(dirname(abspath(__file__))) — how a packaged module names its repo root, and the form all FOUR __file__ sites in the runtime package use — overshoots by one level and the world directory is not found. It is a FORK, not a bug to take: decide-nilpy-dunder-file-for-a-compiled-program is the owner's own decision, says <original module basename>, never mentions packages, and its payoff sentence cannot both hold and let those four sites work. Carried as decide-n-what-does-dunder-file-mean-for-a-module-inside-a-package (wired above) with the measurement and three options. FILED AND DELIBERATELY NOT FIXED: bug-n-an-imported-module-s-star-star-never-installs-pypowhook — TWO independent defects (the pyWantsPow token scan runs before PyParseImportRun; and an imported .py is a UNIT whose initialisation section runs before the main body that holds the assignment), each sufficient alone, with a control separating them: with ** in main too, import time printed 1.9952623149688793 and post-start printed CPython's exact 1.9952623149688795. Visible cost is ONE ULP, not a crash — do not rank it as though it were the ValueError that led to it. REGRESSION ROWS WIRED (this umbrella is still not a gate; these are ordinary test-nilpy rows, not lekkerzeilen): io.open in a module that rebinds bare open — world.py's own shape, because without the rebind the row passes even if io.open falls through; three __doc__ fixtures, one of them a tab/whitespace-only/deeper-than-minimum dedent; and test_nilpy_pow_zero_base_in_an_imported_module, which contains no ** of its own on purpose. Landed: 4db262e08 (io.open + __doc__) and the pypow_cx row beside this edit. The earlier narrative of this summary — the with ... as diagnosis lesson is the one still worth reading — moved to the body section dated 2026-09-12, marked superseded. |
bug-a-a-nilpy-object-allocation-takes-no-heap-lock-on-x86-64-threadsafe, bug-n-a-bytearray-bound-to-a-c-pointer-parameter-passes-the-object-pointer-not-the-data, bug-n-a-callee-declared-below-its-caller-gets-the-argument-by-the-wrong-abi, bug-n-a-list-bound-to-a-c-pointer-to-pointer-parameter-passes-the-object-pointer, bug-n-a-method-s-non-constant-default-is-none-when-the-call-is-inside-the-class, bug-n-a-method-that-calls-a-method-with-a-list-argument-loses-its-own-result, bug-n-a-star-unpack-in-a-with-header-reads-its-own-temp-before-it-is-filled, bug-n-a-staticmethod-or-classmethod-is-unreachable-through-a-class-held-as-a-value, bug-n-getattr-cannot-see-a-method-and-segfaults-through-a-dynamic-receiver, bug-n-os-environ-and-os-sep-are-not-values, decide-n-what-does-dunder-file-mean-for-a-module-inside-a-package, feature-b-pil-is-a-python-surface-over-the-rtl-png-decoder-not-a-new-decoder, feature-n-a-method-call-cannot-take-an-argument-after-a-star-unpack, feature-n-a-runtime-dispatched-method-call-is-capped-at-four-arguments, feature-n-the-module-docstring-is-consumed-and-discarded-so-doc-is-undefined, task-b-write-the-lekkerzeilen-pxx-platform-backend |
| umbrella-lekkerzeilen-runs-at-15-fps | E | 95 | umbrella | 15 FPS IS RETIRED AS A TARGET -- OWNER, 2026-09-22, HIS OWN WORDS: 15fps is not written in stone, just a wishful figure. NOTHING MAY BE RANKED BY DISTANCE TO IT, and any ticket justified as gets us to 15 needs re-justifying on something else. The slug keeps the number so citations resolve; the GOAL is now: find the main performance issues. HIS SEQUENCE IS FINISH-THEN-MEASURE, NOT MEASURE-THEN-PICK: i think when all current known issues are done, just profile it again. So do NOT grow new perf tickets off the 2026-09-21 --region rijn profile -- that scene has now produced THREE levers measuring to approximately zero on the scene that ships, and the re-profile must be on ROOFS. WHERE WE ACTUALLY ARE, and these rows stand on their own without any target: pxx median 1.887 fps = 530 ms/frame over 19 windows on world/roofs, vsync ON, audio ON, quiet box; CPython 3.14.4 on the SAME scene, box and session, median 37.140 fps (27 ms) over 414 windows. THAT 19.7x AGAINST A WORKING ORACLE IS THE REAL FINDING and it needs no wishful number: the gap is OURS and it is structural. lekkerzeilen@devdocs/perf/ROOFS-2026-09-22.md, 62859ff. MEASUREMENT RULES THIS TICKET HAS PAID FOR, all still binding: a factor is a property of the SCENE and the MACHINE STATE it was measured on (four factors in one day, every one arithmetically correct on a different frame); treat a single timing as ~20% soft and a RATIO of two arms with different load sensitivity as UNBOUNDED until both run interleaved on a quiet box; and NEVER rank by per-call cost x calls per frame -- 7a predicted 12% from a 12.3x per-call win and measured 3.3% inside its own 8.8% noise, because Soundscape.update caps the audio queue at a fixed BYTE target so work per frame does not grow as the frame slows. MEASURED AS NOT THE CAUSE: vsync, audio, the RNG (8.16x per shift, in-situ value nil), inverse trig (0.22%), and heap contention (the render loop is ONE thread; the 16.5% rijn heap-lock row is UNCONTENDED and a 400k-object A/B bought +4.9% with overlapping error bars). STILL LIVE: the computed-getattr flag has a shippable off switch (fn = lib[name], zero computed sites) worth 112,180 bytes STATIC with its runtime value UNMEASURED; and the managed-local release sweep, whose inline nil-test landed at 56.8% off the per-slot cost against a 55.8% prediction, x86-64 and SXR_STR only. DO NOT REVERT the computed-getattr widening -- it stops a SIGSEGV in imported modules. |
perf-a-every-return-releases-every-managed-local-even-the-untouched-ones, perf-n-one-computed-getattr-in-any-imported-module-boxes-every-method-in-the-program, perf-o-the-variant-hidden-dest-clear-is-a-proc-call-where-the-store-arm-uses-an-inline-blob, task-e-decompose-a-lekkerzeilen-roofs-frame-so-two-perf-tickets-stop-guessing-at-their-own-prize |
| umbrella-managed-memory-is-correct | A | 75 | umbrella | GOAL, not a unit of work. The owner named memory management as ranking above float-bit and parity work. This is the axis a real program hits hardest and where a wrong answer is silent: a leak, a double free, a refcount that disagrees with itself. Correctness is the case here -- the perf profile is deliberately NOT the argument. | bug-a-a-generator-body-raising-past-a-managed-temp-is-not-covered-by-the-unwind-landing-pad, bug-a-a-generator-instance-is-not-freed-when-an-exception-escapes-the-for-in, bug-a-a-shared-ansistring-handle-in-a-parallel-loop-is-11x-slower, bug-a-an-interface-as-cast-retains-on-every-execution-and-releases-once-per-scope, bug-a-managed-locals-leak-on-an-unwind-on-wasm32-and-xtensa, bug-a-only-the-pascal-frontend-ever-asks-for-an-unwind-landing-pad, bug-a-pxxalloc-does-not-check-the-mmap-return-so-oom-arrives-as-an-anonymous-segv, bug-a-string-release-has-two-implementations-that-already-disagree, bug-a-two-different-binaries-both-pass-the-self-host-fixedpoint-for-one-source-tree, bug-nilpy-a-generator-instance-leaks-its-locals-and-argument-cells, bug-nilpy-a-managed-local-in-an-unwound-frame-is-never-released, bug-nilpy-except-x-as-e-still-leaks-every-exception-the-bare-arm-fix-did-not-cover-it, feature-a-record-rtti-descriptors-for-initializearray-and-finalizearray, feature-a-reentrant-heap-lock-and-per-thread-arenas, feature-pascal-management-operators-copy-and-addref, feature-pascal-management-operators-nested-and-array |
| umbrella-pxx-compiles-fpc-itself | P | 85 | umbrella | PIN FLOOR MEASURED, 2026-09-17 (frankB), v410 c599e8546121 -> v411 bc884808fda5, ONE tree 8d9d69bdc / one lib/rtl / one corpus -- AND IT IS THE FIRST PER-UNIT JOIN THIS TICKET HAS EVER CARRIED. The floor moved 21 / 10 / 176 -> 22 / 10 / 175 (gain: versioncmp). ZERO REGRESSIONS ACROSS 207 UNITS, which is the one sentence three numbers cannot produce -- equal totals are satisfiable by a gain and a regression that cancel and nobody here had ever checked. THE +1 UNDERSTATES IT: 29 units changed their FIRST ERROR and 134 more sat at the identical wall with errs= risen, 134 up and 0 down, which reads as more breakage and is the OPPOSITE. Detail-dumped under both binaries on 5 of 5 sampled (aasmbase aasmdef comprsrc nmem pexpr): under v410 each reported exactly two errors, BOTH inside x86_64/cpuinfo.pas, seeing ONE file; v411 fixes the second (cpuinfo.pas:281 too many array initializer elements) and the compiler then walks into FOUR files it had never reached. THE cclasses.pas SHAPE FROM THE OTHER SIDE: every previous row here recorded a cleared wall delivering its population to the next wall in the SAME file; this one sends it into DIFFERENT files, so it surfaces as errs= rather than as units and no first-failure census could see it. The corpus did not merely gain a unit, it became MORE INFORMATIVE. Quantifier NOT claimed: 5 of 5 sampled, not 134 of 134. NEWLY VISIBLE STRATUM BY NAME, none of which had a wall row: globals.pas:1095 no overload of Replace for (AnsiString,ShortString,Integer); globals.pas:1742/1755 init/done not callable as procedure variables; cmsgs.pas:124 undefined variable (fail); cmsgs.pas:459/460 SetCodePage; msgtxt.inc:347 too many array constant elements -- and per this file own rule they are NOT ranked on how many units name them. TWO INSTRUMENT FAULTS FOUND AND FIXED IN THE SAME RUN: the A/B originally specified (pinned vs compiler/pascal26) was a TAUTOLOGY because v411 was minted from this tree -- both bc884808fda5 -- and tools/fpc_corpus_ab.sh REFUSED it on equal sha256 before anything ran; and the join first printed moved=163 when only 29 were a different first error, a 5.6x overstatement from keying on the whole row with errs=N in it, now split into WALL-MOVED and ERRS and cross-checked by an independent Python pass over the same leg files. PREVIOUSLY -- SEVENTEENTH CONSECUTIVE NULL ROW, 2026-09-17 @ c86e8b29d (binary bc884808fda5): 22 / 10 / 175 -> 22 / 10 / 175, UNCHANGED, and PREDICTED CORRECTLY this time. An enum typecast now folds in a constant expression (6eb1db8b4) and in a CASE LABEL, its sibling spelling (c86e8b29d) -- clearing cgbase.pas:401. All 14 units behind it moved to TDoubleRec and NOT ONE converted, cgbase included. The prediction was recorded first: 'another null row; at most cgbase itself converts, most likely unchanged.' THE RUN BEFORE THIS ONE WAS DISCARDED AS CONTAMINATED: I rebuilt compiler/pascal26 while a sweep was in flight, so chunk 0 used one binary and chunks 1 and 2 another; it completed with a plausible tally and is not reported. The probe invokes the binary per unit, so a rebuild IS the instrument. THE STATE THIS LEAVES, and it is the actionable part: TDoubleRec is now the first failure of 155 of 175 failing units (89%). The case for taking it next is NOT that count -- seventeen rows here say a count is a queue position -- it is that THE CORPUS IS BLIND BEHIND IT: at 89% there is almost nothing left to learn from another sweep until it moves, and every other wall is now too small to be informative about anything but itself. It is feature-b-rtl-has-no-tdoublerec, already in blocked-by, and Track B / lib/rtl, which reaches from the LIVE tree and so delivers WITHOUT waiting for a pin -- unlike both compiler fixes landed tonight. That pairing (the only wall wide enough to see past, and the only kind that is live immediately) is the strongest case this umbrella has had for a named next lever, and it rests on blindness rather than on a unit count. PREVIOUSLY -- SIXTEENTH CONSECUTIVE NULL ROW, 2026-09-17 @ efe06a903 (binary 1e1d3ce55597): 21 / 10 / 176 -> 22 / 10 / 175. An old-style object may now have a CONSTRUCTOR and a DESTRUCTOR -- pxx already hard-errors on both routes to a VMT (an ancestor, and a virtual/dynamic/override/abstract directive), so every object that compiles at all is VMT-less BY CONSTRUCTION and a constructor on one is a plain method. ONE unit, zero regressions -- AND IT RETIRES THE INSTRUMENT THIS FILE SPENT SIX NULL ROWS ASKING FOR. The every-failure census built 2026-09-16, called here 'the first proposal with a complete failure set behind it instead of a queue position', said this wall would deliver 18 units; the prediction was written out BEFORE the run as 'between 5 and 18, and if the yield is 0 the instrument is no better than the first-failure census'. IT DELIVERED 1. The reason is exact and is the transferable half: A COMPLETE-FAILURE-SET CENSUS IS COMPLETE ONLY FOR THE SUBJECT UNIT -- every unit it IMPORTS is still truncated at that unit's own first failure, so the census inherits first-failure blindness ONCE PER IMPORT. The object diagnostic is raised in versioncmp.pas (144 of 161 reports), cgbase.pas (14) and cmsgs.pas (3), none of them usually the subject. The join is 18 = 1 + 14 + 3: versioncmp declares its object in ITS OWN source and converted; the 14 cgbase importers moved 25 lines further down the SAME file (:376 -> :401, an enum typecast in a constant expression, now bug-p-an-enum-typecast-is-not-a-constant-expression, three-line repro type TE=(a,b,c); const K=TE(2);); the 3 cmsgs importers moved 77 lines further down (:46 -> :123, fail, the standard procedure that exists ONLY inside an old-style constructor -- a wall this fix created the reachability for). That is the cclasses.pas 895->1327->1726 shape for the FIFTH time, defeating the instrument built to detect it. WHAT ACTUALLY PREDICTED IT WAS THE STUB: the 2026-09-16 stub3 arm in decided/decide-old-style-object-types.md said 22 units for this exact change and the fix delivered 22 -- a stub removes the wall for the IMPORTING units too, so their next failure becomes visible, which no amount of diagnostic recovery achieves. Where a stub is cheap it beats a census, and here the two disagreed 18x. ALSO RECORDED: of the 17 units that are neither TDoubleRec nor the object wall, 10 are PURE RTL gaps (PUnicodeChar+cp1251 7, unixcp 1, heaptrc 1, swapendian 1 -- all five names checked against lib/rtl rather than assumed) and 2 more are RTL plus a Track P gap; the body said 12 for one evening and the arithmetic beside it summed to 14, because entryreal_bytes was miscounted as RTL when entfile.pas:371 declares it as a LOCAL type used as a typecast -- corrected in place with the mechanism. Per WALL 5 an RTL wall is LIVE without a pin while a compiler wall is not, so that is where delivered units are cheapest. PREVIOUSLY -- FIFTEENTH CONSECUTIVE NULL ROW, 2026-09-17 @ 17b8561f2: 21 / 10 / 176, IDENTICAL TOTALS ACROSS 25 COMMITS TOUCHING compiler/ OR lib/ -- AND IT RETIRES A PREDICTION. decided/decide-old-style-object-types.md said the 138 blocked units "do not scatter -- they land as one group on a fifth wall, globals.pas:502" and instructed that this Track P lever be taken first because it "gates the same 138". It was taken (frankS, fa397c761, 2026-09-16), which is NOT an ancestor of 9281da35b where the previous totals were measured, so this run tests the claim rather than repeating it. globals.pas:502 is now the first failure of ZERO of 207 units and units-OK moved by ZERO: the 138 were QUEUED behind it, never gated by it. FIVE walls cleared between the two measurements (tcompilerwidechar 0728155f2, charset 6bc01579f, sizeof-of-a-PARAMETER a931bef4d, TExecuteFlags, expected operator eaf776dd8) and the whole population is now on x86_64/cpuinfo.pas:36 -- TDoubleRec has gone 4 -> 132 -> 140 across three measurements while buying nothing, the cclasses.pas one-file histogram for the fourth time. THE 21-UNIT BOTH-OK SET IS NOW RECORDED BY NAME in the 09-17 section; it had only ever been a count, and the by-name join is the half this file's own method notes call valuable. Three precise one-unit Track P walls replaced two anonymous ones (nld.pas:700 supported_optimizerswitches, nadd.pas:1352 bestrealrec, hlcgobj.pas:4156 aintmax) -- and those LINE NUMBERS are a fix (ec8a4d88c): the evaluator reported the lexer's position, not the directive's, so the probe had said 1334/1112/1821. PREVIOUSLY: WALL 15 (charset) CLEARED 2026-09-16 -- lib/rtl/charset.pas, FPC's codepage registry, the largest LIVE head remaining -- AND IT IS THE FOURTEENTH CONSECUTIVE NULL ROW IN ITS STRONGEST FORM YET: not merely units-OK unchanged but ALL 207 ROWS BYTE-IDENTICAL, verdict and first error alike, proven by A/B over the whole corpus rather than inferred. Totals 21 / 10 / 176 on both legs. AND THE WALL-13 AND WALL-14 TABLES IN THIS FILE RECORDED 22 / 10 / 175 AND WERE WRONG ON ARRIVAL: the BOTH-OK count was derived from the units-OK (stubbed) row directly above it and PXX-FAIL by subtraction (207-22-10=175), never read off the probe -- corrected in place, with the mechanism, because a stale row is not inert and this one was consumed as a premise by the next prediction that cited it. MEASURED 2026-09-16 WITH PXX_CORPUS_DETAIL, AND IT ANSWERS THE SIZE QUESTION THIS UMBRELLA ASKED FOR SIX NULL ROWS: 132 of 207 units report EXACTLY TWO errors and nothing else -- unknown type: TDoubleRec (x86_64/cpuinfo.pas:36) and too many array initializer elements (:281) -- BOTH IN ONE FILE, with the 20-error recovery cap not in play (one unit in the whole corpus reaches it). So the 132 are not merely QUEUED behind TDoubleRec; those two are their COMPLETE reported failure set. The second wall had no ticket and now does: bug-p-an-array-constant-with-a-set-element-type-cannot-be-initialised, the UNFIXED SIBLING of the record-constant arm that 138604b5e fixed. THE SIXTH NULL ROW IS CONFIRMED AND WAS PREDICTED BEFORE THE RUN: TDoubleRec ALONE delivers ZERO units -- 0 of the 132 have it as their only error. Expect a third wall in the same file rather than 132 units; what is new is that this is the first proposal with a complete failure set behind it instead of a queue position. Totals at 9281da35b: 21 / 10 / 176 (BOTH-OK / ORACLE-NO / PXX-FAIL). Owner-set direction 2026-09-09: 'we are going to be more application driven, not just hunting down theoretical bugs but just.. let's get stuff rolling. so, we had practical targets like busybox. or compiling FPC itself.' NO TICKET FOR THIS EXISTED ANYWHERE IN devdocs/progress -- measured, zero hits. FPC's own compiler is ~400k lines of Object Pascal written by people who were not testing us, which makes it the largest and least self-serving Pascal corpus available, and it is the application-driven form of exactly what Track P has been doing by hand: every bug the P seats hunted from the backlog tonight would have been found by this target, in the order that actually matters. BLOCKED-BY IS GROWN BY ATTEMPTING, NEVER BY TRIAGE -- CLAUDE.md: 'Each failure names a ticket in the order it actually matters. What the attempt never touches was not blocking real-world usage.' STATE at 4c7c88d36 (attempt 7, probe #12): 20 of 207 units compile under both fpc and pxx, 10 are ORACLE-NO and can never be evidence about us, 177 fail. THE FOUR CONSECUTIVE NULL ROWS ENDED AND THEY ENDED CHEAPLY: cclasses.pas compiles, and the per-unit join says the +3 is exactly that unit plus its two direct dependents (crefs, rabase) -- so clearing the wall 150 units were stacked on was worth THREE, which is this umbrella's own queue-position finding confirmed rather than refuted. It took two bugs, both found by converting one halting diagnostic to ErrorRecover so a unit reports EVERY failure instead of the first: a method parameter typed through a forward pointer alias never matching its own body (ee560d0ad), and the element-count form of Initialize/Finalize (d095cb08d), which had been a DELIBERATE refusal. SEVEN walls cleared now; BOTH-OK has gone 9 -> 15 -> 15 -> 15 -> 15 -> 18. THE TExecuteFlags WALL IS CLEARED (2026-09-11, frankH): sysutils gained ExecuteProcess and TExecuteFlags, and cfileutl.pas:136 no longer stops anyone. It bought ZERO units -- a FIFTH null row -- and an A/B on ONE binary shows why: cfileutl, rgobj and aasmbase were all stopped at that SAME LINE, and they now stop at unknown type: TDoubleRec (x86_64/cpuinfo.pas:36) and undefined variable (IsATTY) (comptty.pas:66) respectively, the latter again ONE line reached by two units. BOTH NEW WALLS ARE NOW SETTLED TOO, SAME EVENING, AND BOTH LANDED ON THE UNIT CYCLE. termio.IsATTY was added (d57a1efaa) and rgobj+aasmbase moved to comphook.pas:251 undefined variable (V_Status). I recorded that as the unit cycle, then CORRECTED myself to 'not the unit cycle, that ticket is done and its minimal shape passes' -- AND THE CORRECTION WAS ALSO WRONG. It IS the unit cycle, in an ORDER-DEPENDENT shape the fixed ticket's fixture structurally cannot express: CycleWaitUnit is a global and ParseUnitImplSection re-enters itself, so a unit named AFTER the cycle-closer in the same clause cleared the pending park. Both of my claims measured a real shape and generalised it to the bug; the quantifier was the invention each time. FIXED 2026-09-11, same evening, and the V_Status wall is CLEARED. TDoubleRec was NOT built: measured instead, and it buys zero, because cfileutl's implementation uses Comphook+Globals and globals alone already fails at that same V_Status; it was re-laned P and repriced 40->25 blocked-by the cycle. So THREE walls cleared or priced in one evening delivered every unit involved into ONE new wall -- which was then cleared too, the same evening, as the order-dependent residual of the unit cycle. globals, rgobj, aasmbase, comphook and cfileutl ALL now reach unknown type: TDoubleRec (x86_64/cpuinfo.pas:36), which is feature-b-rtl-has-no-tdoublerec, already in blocked-by and already repriced. MEASURED, AND THE SIXTH NULL ROW IS CONFIRMED -- A/B over all 207 units on TWO BINARIES differing by exactly 401c00f2b: BOTH-OK 21 before and 21 after, ZERO units newly compiling, ZERO regressed, and 101 of 207 units changed their first error -- ALL 101 from undefined variable (V_Status) and ALL 101 to unknown type: TDoubleRec. One wall into one wall, nothing scattered, half the corpus moved and the conversion rate was zero. TDoubleRec is now the first failure of 132 of 207 (64%). That is the largest single-wall transfer this umbrella has recorded and it is the cleanest possible statement of the queue-position finding: a wall's population counts units QUEUED behind it, never work. A SIXTH NULL ROW IS THE DEFAULT EXPECTATION FOR THE NEXT RE-RUN AND IT IS STATED HERE BEFORE THE RUN, not after: five walls in a row have converted at 0, 3 and 2 units, and every unit this one freed landed on the same next wall, which is the signature of a queue rather than a population. AND THE INSTRUMENT THIS UMBRELLA SAYS NOBODY BUILT ALREADY EXISTS: the compiler recovers up to MAX_REPORTED_ERRORS=20 semantic errors per unit, and tools/fpc_compiler_corpus_probe.sh pipes it through head -1. Taking that off shows the wall BEHIND the first one for free -- behind cfileutl's TDoubleRec sits too many array initializer elements at cpuinfo.pas:281 -- so the every-failure-per-subject census this umbrella has wanted for five null rows is a harness change, not a compiler feature. DO NOT RANK ANY OF THEM ON UNIT COUNT: attempt 7 measured the conversion rate of a cleared wall at three units. A second row went the same evening -- a set-valued record field (138604b5e), which is how tokens.pas writes its ~400-row token table -- and probe #12 says it bought TWO (tokens, rescmn) while eight more moved up to the TExecuteFlags wall. THREE WALLS, THREE JOINS, YIELDS OF 3 AND 2: a wall's population says how many units are QUEUED behind it, and the units that turn BOTH-OK are the ones for which it was the LAST wall. Near-disjoint sets; only the second is worth a number. |
bug-p-a-conditional-directive-cannot-evaluate-in-over-a-set-constant, bug-p-a-conditional-directive-cannot-read-a-const-whose-value-is-not-an-integer-literal, bug-p-a-conditional-set-constant-whose-terms-live-two-units-away-declines, bug-p-a-method-parameter-typed-through-a-forward-pointer-alias-never-matches-its-own-body, bug-p-a-semantic-diagnostic-in-a-used-unit-names-no-file-at-all, bug-p-a-set-valued-record-field-cannot-be-written-in-a-record-constant, bug-p-an-array-constant-with-a-set-element-type-cannot-be-initialised, bug-p-an-enum-typecast-is-not-a-constant-expression, bug-p-compile-time-info-macros-are-not-implemented-and-silently-yield-zero, feature-b-rtl-has-no-tdoublerec, feature-b-rtl-has-no-termio-unit-and-no-isatty, feature-b-sysutils-has-no-executeprocess-and-no-texecuteflags, feature-p-legacy-value-object-types, feature-p-the-element-count-form-of-initialize-and-finalize, feature-p-unaligned-is-a-transparent-lvalue-not-a-function |
| umbrella-pxx-hosted-beyond-linux | A | 25 | umbrella | GOAL, not a unit of work. 'Run a minimal system with compiler' -- pxx HOSTED somewhere that is not Linux/x86-64, not merely cross-emitting to it. Self-host is proved here every ~12s by the build; the goal is that same property on another kernel. OpenBSD is the nearest rung and the only one with tickets today; minix 2/3 and Windows have NONE, which is information, not an oversight. | decide-openbsd-pinsyscalls-vs-the-rt-sigreturn-residual, feature-port-openbsd-libc |
| umbrella-sizeof-is-one-answer | A | 75 | umbrella | GOAL: a program can trust SizeOf. FillChar(x, SizeOf(x), 0) and Move(a, b, SizeOf(a)) are correct for EVERY type in every frontend, and file of T can write a layout that reads back. Today they are not: SizeOf answers 8 for every string[N] while pxx's OWN layout engine gives that type 18, so FillChar on an array[0..2] of string[10] clears 24 of 54 bytes and leaves a[2] intact -- silent, and correct under FPC so no differential probe sees it. Root cause is measured and structural: FOUR functions answer how big is this type, each adding one more parameter because the kind alone was not enough -- TypeSlotSize(tk) at 363 sites, TypeStorageSize(tk, recId), SizeOfSlot(tk, cap), FrozenStrSlotSize(tk, cap). SizeOfSlot's own comment says it: A FROZEN STRING'S SIZE IS NOT A FUNCTION OF ITS KIND. Two is a smell, three is a design flaw; this is four, plus duplicated builtin type tables in A, N and P that disagree with each other. |
bug-a-method-pointer-record-is-hard-sized-16-bytes-on-32-bit-targets, bug-a-pascal-nilpy-rust-and-zig-over-align-an-8-byte-member-on-i386, bug-c-sizeof-of-a-pointer-to-array-struct-field-answers-the-pointer-size, bug-c-sizeof-reaches-a-pointee-through-one-spelling-only, bug-n-nilpy-carries-its-own-copies-of-the-float-type-table, bug-p-a-string-n-element-loses-its-capacity-in-three-container-shapes, bug-p-a-user-type-whose-name-shadows-a-builtin-is-unusable, bug-p-sizeof-answers-pointer-width-for-a-string-n-that-occupies-more, bug-p-sizeof-of-a-type-name-is-settled-against-a-kind-that-cannot-express-the-size, bug-p-sizeof-rejects-twelve-type-names-that-a-declaration-accepts, compat-pascal-four-type-sizes-disagree-with-fpc-and-every-value-agrees, feature-p-implement-the-real-tyshortstring-byte-prefix-layout, refactor-a-the-const-cast-width-table-is-the-third-copy |
| umbrella-track-p-and-a-have-no-open-bugs | A | 80 | umbrella | GOAL, not a unit of work. Owner, 2026-09-07: 'i want bug report on track P and A to be empty.' Census AT CREATION, by folder rather than by a glob: 111 open bug tickets -- P 37 (backlog-pascal 32, working 5), A 74 (backlog-core 72, unfinished 1, working 1). The number is the deliverable and it is also the thing most easily faked, because a ticket moved to low-prio/ or rejected/ leaves the count exactly as a fixed one does. So this umbrella carries the census method and demands the SPLIT -- fixed vs correctly re-filed -- in every report against it. Two thematic clusters carry the bulk and are where the leverage is: managed types (27 across both tracks) and cross-target/backend (19, all A). | — |
| umbrella-wasm-is-a-real-platform | A | 25 | umbrella | GOAL, not a unit of work. wasm is named in the goal's platform list and is the non-Unix platform with the most work already landed -- the wasm branch is merged into master. Two halves: emit correct wasm32, and HOST the compiler under a wasm runtime. The hosted half already has a live crash (node, not wasmtime). | bug-a-emitzeroframeslot-has-no-wasm32-arm, bug-a-wasm32-has-no-variant-ir-arms-so-any-variant-assignment-traps, bug-c-no-c-program-entry-stub-for-wasm32-so-no-c-program-can-target-it, bug-wasm-hosted-compiler-crashes-node-but-not-wasmtime-on-a-full-compile, feature-t-run-the-wasi-slices-under-wasmtime-as-a-strict-second-host, feature-target-wasm |
backlog-core (172)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| bug-a-64-bit-multiply-overflow-is-unchecked-under-q-plus-on-riscv32-and-xtensa | A | 25 | bug | {$Q+} does not catch a 64-bit*64-bit product that overflows Int64 on riscv32 or xtensa; x86-64 raises Runtime error 215. Both 32-bit backends print the wrapped value and carry on. Pre-existing on riscv32 (its own source says unsigned checked mul is deferred) and found while bringing xtensa to parity with it, so this is the gap they SHARE rather than anything new. | — |
| bug-a-a-bad-value-for-a-known-option-is-reported-as-an-unknown-option | A | 30 | bug | --target=x answers unknown option: --target=x, but --target is a known option with a bad value. Same for --xtensa-cpu= and --esp-profile=. The message sends the reader to hunt a typo in the FLAG NAME when the flag is right and the VALUE is wrong, and it makes every value-taking option indistinguishable from a nonexistent one to any tool or person probing the CLI. |
— |
| bug-a-a-case-of-string-on-a-widestring-matches-nothing-under-pxx-wide-payload | A | 35 | bug | ws := 'abc'; case ws of 'abc': ... takes the branch by default and under fpc, and takes NO branch under -dPXX_WIDE_PAYLOAD. Pure ASCII, so it is not the encoding question the chore ticket exists to settle. It is the ENTIRE blast radius of forcing the define across the conformance corpus: 406 pass / 5 fail against 411 / 0, and all five failures (tcase0/12/13/28/29) are this one mechanism. Cause not measured -- print the widths the comparison receives before fixing. |
— |
| bug-a-a-char-array-in-a-string-context-stops-at-the-first-nul-and-fpc-does-not | A | 35 | bug | __pxxCharArrayToStr stops at the first #0 within the array's capacity, and its own comment says that is FPC's rule. Measured 2026-09-06 against fpc 3.2.2: it is not. For a: array[1..4] of Char = 'pq' (bytes 112 113 0 0 on BOTH compilers — the padding is identical), fpc's Writeln(a) emits all four bytes and Length(string(a)) is 4; pxx emits two and answers 2. Two observables, one cause. The comment is the load-bearing part of this ticket: it asserts a measurement that is false, so a reader who checks it stops looking. |
— |
| bug-a-a-class-named-after-a-used-unit-cannot-be-constructed-from-outside-that-unit | A | 45 | bug | MEASURED 2026-09-14, reduced to two 20-line units. A class declared in unit X whose NAME equals the name of a unit X uses cannot be constructed from any other unit: Widget.Create(7) answers undefined variable (Create), because the unit qualifier wins over the locally-declared type. FPC resolves this the other way -- a type declared in the current unit shadows a used unit's NAME -- and the dialect is the target, so this is a real divergence rather than a style question. THE COLLISION IS NOT HYPOTHETICAL AND CANNOT ALWAYS BE DESIGNED AWAY: lib/rtl/pil.pas has to declare a class called Image because Python writes from PIL import Image and Image.new(...), and it has to use lib/rtl/image.pas because that is where TImage lives. Both names are forced by something outside our control. IT IS LOUD FROM OUTSIDE AND THAT IS THE GOOD CASE -- a caller gets a refusal naming Create. Inside the declaring unit the same spelling compiles and does something else, which is filed separately and is the dangerous half; the two were originally diagnosed as one bug and are not (see bug-a-a-class-var-declared-before-an-instance-field-corrupts-the-instance-layout, which turned out to be the actual cause of the misbehaviour inside the unit). WORKAROUND IN USE: declare the class under an internal name and expose the Python-facing name as a type alias (TPILImage = class ... end; Image = TPILImage;), which works and through which NilPy still resolves the class correctly. Registered in devdocs/dev/track-b-workarounds.md. PRIO 45 rather than higher because it refuses instead of miscompiling, the workaround is one line, and the collision needs a deliberate name clash to hit. |
— |
| bug-a-a-cloned-thread-still-inherits-the-parents-fs-base-on-every-target-but-x86-64 | A | 45 | bug | > | — |
| bug-a-a-comment-claims-a-cow-check-for-dynamic-arrays-that-was-deleted | A | 25 | bug | — | |
| bug-a-a-dynamic-array-value-can-be-assigned-to-a-record-variable | A | 30 | bug | var p, q: array of TRec; r: TRec; then r := p + q COMPILES and stores the concatenation's array handle into r. Pre-existing and DELIBERATE at the point it is decided: AssignSideKind bails on any node with NodeDynDepth > 0, because it cannot type an array side and its own header ranks a false reject as the worse defect. So the abstention is principled and the accept is the cost of it. fpc 3.2.2 refuses (Operator is not overloaded: TRecArr + TRecArr) but for a different reason -- fpc has no dyn-array + without {$modeswitch arrayoperators}, while pxx's concatenation is a deliberate feature, so this is NOT a parity row and must not be fixed by removing concatenation. Verified pre-existing against pin stable_linux_amd64/default/pinned. The fix is for the assignment check to be able to TYPE an array side, which is the same missing concept as bug-p-an-enum-or-array-type-cannot-be-named-as-an-operator-operand: an array's TypeKind IS its element's, so nothing downstream can tell array of TRec from TRec. |
— |
| bug-a-a-foreign-thread-shares-the-main-thread-s-heap-magazine | A | 65 | bug | RE-MEASURED AND STILL LIVE 2026-09-19 (frankS) at HEAD, and the SCOPE HAS NARROWED since filing: a thread that never runs pxx's own entry code — neither the __pxxclone stub nor PxxPthreadStart — inherits its creator's gs, so every gs: slot it touches is the creator's. "A LIBC PTHREAD" IS NO LONGER THE RIGHT DESCRIPTION AND WAS WHEN THIS WAS FILED: 934ba0418 (2026-09-14, 13 days after the original measurement) routes pxx's own threads through pthread_create with PxxPthreadStart as the start routine, and that trampoline mmaps a block and installs it with arch_prctl(ARCH_SET_GS) exactly as the clone stub's child leg does. So a pthread pxx created is FINE; what is still broken is a thread whose start routine pxx never wrapped — a direct external 'libpthread.so.0' pthread_create, or a thread a linked external .so starts on its own. Measured today with BOTH routes in one program as each other's control: four BeginThread threads report four DISTINCT bases, four threads from a direct libpthread pthread_create all report the MAIN thread's base (10 duplicate pairs of 10). The original measurement stands unchanged because its subject, test/test_multithreading.pas, declares pthread_create as external 'libpthread.so.0' and is therefore on the still-broken route. ORIGINAL: gs_base is BSS_TLS_MAIN on all five threads of test_multithreading. The CRASH this caused is fixed (6b3b54ce4 made the heap magazine's guard atomic, SO A SHARED MAGAZINE IS CORRECT -- this clause is the one most often read past, and on 2026-09-22 a seat escalated this ticket as an unguarded data race on the magazine, which is the opposite of the record. THIS IS AN ALL-CLEAR AND IT IS CONDITIONAL: it holds BECAUSE that guard is atomic, and it is VOID the moment the guard stops being atomic. A reader who finds a non-atomic magazine guard should treat this sentence as RETIRED, not as reassurance -- dated 2026-09-22); what is left is that the TLS block is not per-thread for foreign threads, which is a design question and touches every slot, not just the magazine. |
decide-a-a-foreign-thread-needs-its-own-tls-block-and-the-bounds-are-the-hard-part |
| bug-a-a-frontend-cannot-see-that-a-backend-calls-library-routines-it-never-mentions | A | 45→70 | bug | FIXED FOR RUST AND ZIG, OPEN FOR THE CLASS. Some backends lower ordinary constructs onto routines that live in builtinheap, and a frontend driver cannot see it: riscv32 routes EVERY integer write through PXXWriteDecW, and the aggregate-result epilogue lowers onto PXXMemMove on aarch64/arm32/riscv32. A driver that does not pull the unit produces a working binary on the targets whose codegen happens to be inline and compiler error: <routine> not found -- an internal-fault-shaped diagnostic -- on the rest, for a program whose source mentions neither strings nor the heap. Measured: fn main(){ println!(\"{}\", x); } ran on x86-64/i386/aarch64/arm32 and failed on riscv32, while the SAME print from Pascal worked there, because a Pascal program pulls builtinheap ambiently. Rust and Zig now ask TargetCodegenCallsHeapRuntime (emit.inc). STILL OPEN: eparser pulls nothing and has the same hole; the predicate is a hand-maintained union of two known mechanisms; and its xtensa entry is taken from cparser.inc's comment rather than measured. |
— |
| bug-a-a-generic-body-takes-its-directive-state-from-the-specialization-site | A | 40 | bug | A generic method body is compiled under whatever directive state is in force where it is SPECIALIZED, discarding the state its own source wrote. Both directions measured with an isolating control: an identical body in a PLAIN class in the same unit raises correctly, so it is generics and not units. Missing-check direction — a body wrapping field:=l in {$R+}, specialized under {$R-}, drops the check and stores 1234 as 210 (fpc-testsuite tgeneric7.pp, whose own comment calls it 'checks proper saving of compiler state'). FALSE-REFUSAL direction, the worse half — a body under {$R-} specialized under {$R+} GAINS a check it never asked for, so a library that deliberately turned checking off is overridden by its consumer. Cause is NOT range-check code: ShiftTokParallel fills a splice gap from the token before it (right for a synthesized token, wrong for a specialization's verbatim template copies), and the specializer already overwrites TokSrcOff/TokSrcLen right after the shift for exactly that reason while leaving the NINE directive states behind. ShiftTokParallel's own comment predicted this caller class and the sibling channel was never carried. NOT reachable through pxx's own RTL today — neither collections.pas nor p256field.pas contains a directive, and both compile and run correctly under a {$R+} consumer — so this is latent here and live for FPC-shaped library code. |
feature-dynamic-compiler-tables |
| bug-a-a-hand-built-com-interface-cannot-be-called | A | 55→65 | bug | An interface value built BY HAND -- a pointer whose first word is a table of code pointers, which is FPC's and Delphi's interface representation -- segfaults when called under pxx. 60-line repro (test-shaped, in the body): pxx dies in PXXIntfIMTOf, fpc 3.2.2 prints 42. The two representations are INVERTED and each compiler is self-consistent: pxx's interface value is the INSTANCE pointer and the IMT is recovered per call from the instance's RTTI blob ([[inst]-8] -> iface table -> IMT, ir.inc's AN_INTF_CALL and builtinheap.pas's PXXIntfIMTOf); FPC's value IS the IMT pointer. The positive control is the mirror and it is exact -- IFoo(Pointer(anObject)) WORKS under pxx and dies with RTE 216 under fpc. The IMT CONTENTS already agree (slot 0 QueryInterface, 1 _AddRef, 2 _Release, then methods, spelled out in builtinheap.pas); only the ROUTE to the IMT differs. No runtime discrimination is available -- the RTTI blob carries no magic word, so a fallback in PXXIntfIMTOf would be a guess, not a check. The discrimination DOES exist at the cast site (the operand's static type is a raw pointer, not a class) and cannot survive the value, because the value is one word with nowhere to put a tag. That is why this is a representation decision and not a patch. Blocks feature-pascal-corpus-generics: TComparer<T>.Default is nil because rtl-generics reaches every comparer through exactly this construct. |
decide-how-a-hand-built-com-interface-becomes-callable |
| bug-a-a-label-inside-a-finally-body-is-a-duplicate-ir-label | A | 20 | bug | > | — |
| bug-a-a-managed-record-function-result-runs-neither-initialize-nor-finalize | A | 30 | bug | MEASURED 2026-09-06. A record with class operator Initialize/Finalize returned BY VALUE from a function runs NEITHER operator for the function's own Result variable: fpc Initializes it on entry and Finalizes the caller-side temp when the statement ends, pxx does neither. So a managed record's invariant does not hold for the one value a factory function produces, which is the ordinary way such a record is constructed -- function NewFoo: TFoo returns something Initialize never saw, and any handle it owns is never released. PRE-EXISTING AND NOT THE AddRef WORK: verified against the PINNED compiler on the same program with the AddRef operator removed (the pin refuses that declaration), and the pin omits the identical two lines. Distinct from the by-value PARAMETER event, which is now correct in both argument shapes. It is the whole reason test_mgmt_operators_addref_nonlvalue_arg.expected is not fpc's output byte for byte -- the two missing lines are exactly this defect, named there, and that fixture MUST go red when this is fixed. |
— |
| bug-a-a-nilpy-function-named-main-is-refused-on-wasm32-only | A | 40 | bug | A NilPy program containing def main() fails to build for wasm32 with wasm: duplicate export \"main\" — two different slots (1770 and 1777) want one name, and builds clean for every other target. The user's function and the module's entry point are both exported as main and nothing renames either. main is the single most likely name in a Python program, so this is not an edge case; it is the first thing a newcomer writes. |
— |
| bug-a-a-nilpy-generator-slice-faults-out-of-bounds-under-wasm32 | A | 45 | bug | test/wasm/check_nilpy_generator_slot.sh is RED at HEAD and has nothing to do with --dce: the slice traps RuntimeError: memory access out of bounds in the wasm runtime rather than printing its two lines. ESTABLISHED PRE-EXISTING BY STASH-AND-REBUILD, not by reasoning: stashed an unrelated wasm change, rebuilt to converged after 1 round(s), and the check fails identically -- so it is not the wasm32 --dce work landing beside it. The check was written for a WRONG-VALUE defect (4 2 against 4 5) and now dies before printing anything, so whatever it originally caught is masked by a fault that arrived later; its own header records the value defect and no longer describes what happens. Found while running the wasm suite for an unrelated change, which is the only reason anyone looked -- check_all.sh reports at least one check FAILED and exits 0, so nothing upstream reddens on it. |
— |
| bug-a-a-pascal-hello-world-is-63kb-after-emission-size-dce | A | 30→70 | bug | THE PROMOTION LANDED AT 39ca6ac2a (frankh-c0, 2026-09-22, third attempt) AND THIS TICKET IS NOT DONE -- its ORIGINAL mechanism is untouched and is now the whole of what remains, stated at the end of this summary. --dce is the default at -O2, wasm32 carved out. PROOF: full tier at that tree, 4959/4962 pass, 2 flaky, RED on two FAILs and NEITHER attributable to the change -- test-nilpy#675 was recorded NEW-RED by Track T's borg run at pin v417 BEFORE the work (a 1-ULP atan2 difference; byte-identical output under --dce/--no-dce/default so the flag cannot cause it) and test-riscv32#180 is red across FOUR hosts in tstate and compiles rc=0 either way. Attempt 2 was six FAILs; this was two, both outside the change. No green tier was claimed. THE OBJECT-MODEL GATE WAS MEASURED AWAY AND IT WAS MINE: the promotion enacts NEITHER half of decide-a-is-a-pxx-object-..., because --emit-obj keeps all 323 exports (312 WEAK FUNC, identical name sets, UND 0) while dropping 269 LOCAL bodies, and model B's collapse needs RE-ROOTING which an -O level does not do. WASM32 IS CARVED OUT for the TARGET's consumption model, not for our tests: six of seven wasm failures were inst.exports.<Name> is not a function, which is what a wasm library IS, and the seventh is flag-INDEPENDENT and already filed. VERIFIED AGAINST frankb-8e's 372dd5113 afterwards (rebuild, every target builds at the default, both correctness rows give their owed codes with no flag, no closure refusal, gate quick GREEN). THE TWO CORRECTNESS REGRESSIONS WERE FIXED BY frankb-8e at 5fccc890a. --fpc-float-errors div0 and --fpc-mem-errors nilread both give their owed 208/216 under --dce now, on all arms (0/208/205/207 and five modes at 216), with --no-dce unchanged as the control. The mechanism was EmitCodeAbsToRdx recording no CodeRef for a call +0 / pop rdx / add rdx, imm32 delta, so the pass neither protected the target nor re-aimed it; the i386 twin and two latent siblings went with it (see bug-a-two-code-to-code-references-are-unrecorded-and-are-safe-only-by-where-they-happen-to-sit, now done). THE EVIDENCE IS THE SLOT BYTES, NOT THE EXIT CODE: before the fix the four deltas were byte-identical in the --dce and --no-dce binaries of one program while the code between them had moved; after, exactly one moves (-66886 -> -24082, 42,804 bytes dropped in that gap) and three do not -- which is the three-survive/two-break split this ticket measured and could not explain. OF THE SIX FAILS FROM ATTEMPT 2, FOUR REMAIN AND NONE IS A CORRECTNESS BUG: two are the test-emit-obj t Hidden control arms whose subject the promotion deletes and whose repair is --no-dce on those rows, NOT weakening the assertion (dropping the row leaves a guard that passes when the symbol is absent); one is test-core#1008, frankb-8e's own wasm32 renumbering guard firing correctly on an unclosed live set; one is not ours (test-riscv32#180, identical rc either way). SO A THIRD ATTEMPT IS: respell those two rows, carve wasm32 out of the default, re-run the tier. THE WASM32 CARVE-OUT IS STILL REQUIRED and is not a defect -- the harness reaches bodies by EXPORT NAME and --dce correctly drops an export nothing reaches, so those rows would arrive as failures and read as evidence against the pass. AND THE TRAP IS UNCHANGED: the self-host fixedpoint converges at the promoted setting and converged while both regressions were live -- it converged at all seven builds of the fix too, including the unfixed ones. It is not evidence about this class and it reads exactly like evidence. THE OBJECT-MODEL GATE IS MEASURED AWAY AND IT WAS MINE (frankh-c0, 2026-09-22): the promotion enacts NEITHER half of decide-a-is-a-pxx-object-a-self-contained-runtime-or-a-translation-unit, so a third attempt says THAT in the commit rather than picking a side. I had written that promoting turns per-object DCE on by the back door and therefore answers the fork silently. The first clause is true and the second does not follow. Measured at fd6965890102, x86-64, --emit-obj of a C TU pulling the runtime: --dce drops 269 of 540 LOCAL FUNC bodies and the export surface is INVARIANT -- 323 exports, 312 of them WEAK FUNC, IDENTICAL NAME SETS, UND 0 both ways; a second TU drops 436 of 489 locals with 2 exports unchanged. The bodies DCE removes are LOCAL and were never part of the object's external contract, so an object still supplies exactly what it supplied before under EITHER answer. The fork's model B -- 298 of 307 exports vanishing -- requires RE-ROOTING at the TU's own exports, which the decide itself had to SIMULATE because --dce does not do it; promoting an -O level does not do it either. SCOPE, stated because the decide flags it: x86-64. The xtensa row is VACUOUS and is not counted -- exports were identical there but --dce dropped ZERO bytes (390 B both), so it cannot tell a preserved export from a pass that did nothing. PROMISE, WITH ITS POPULATION THIS TIME: at tree fd6965890102, x86-64, program h; begin WriteLn('hello'); end., code= from the ok line, 67,541 B -> 18,790 B, -72.2%. That does not refute the earlier -66% / 74,096 -> 24,944 row -- different tree, moving floor -- so both stand, each with what it measured. THE ORIGINAL MECHANISM THIS TICKET NAMES IS STILL UNTOUCHED AND IS NOT THE BLOCKER: PasApplyDefaults defines PXX_MANAGED_STRING unconditionally so every Pascal program still PULLS builtinheap, and DCE removes the consequence rather than the pull; frankS scoped that half on 2026-09-18 (-98% code on bare esp32c3). THE STATED BLOCKER IS ANSWERED AND IT WAS NOT THE BLOCKER (frankb-8e, 2026-09-22, tree fda77c48b8ee): string lexes as tkString_T, a KEYWORD token (paslexer.inc:166), not tkIdent, so a token-kind test reaches it; with that plus eight further type names, PromoInt and the string-valued intrinsics, an evidence scan BUILDS AND RUNS -- x86-64 hello 25016 -> 4472 B (-82.1%), xtensa --esp-profile=bare code 860 -> 58 B and procs 88 -> 0 (-93.3%), riscv32 --esp-profile=bare 336 -> 20 B (-94.0%), all four rows measured against this tree, the pin agreeing on both bare rows. THE REAL BLOCKER IS ORDERING AND NO TOKEN SCAN CAN REACH IT: EmitAnsiStringRuntime is decided at pasparser_prog.inc:1977, while the ambient RTL units that are THEMSELVES written over managed strings are pulled AFTER it -- at :2163 and, for units, from a SECOND pull site at pasparser_proc.inc:7422 -- so var p: PChar, var c: WideChar and var s: set of Char each refuse inside lib/rtl/textfile.pas:1344 with no string in the program at all. The evidence arrives after the decision. WHAT WOULD SPRING IT: any construct that pulls an ambient unit whose body calls a string stub, which is why the fix is to settle the emission after unit pulls (or emit on demand), not to add names. THE FAILURE DIRECTION IS CONFIRMED SAFE AND IS NOW MUCH BETTER EVIDENCED THAN THE ONE riscv32 ROW BELOW: six string shapes x six targets refused 36 of 36 with 0 built, by TWO independent guards (IREmitCodeCall's addr=0 refusal on x86-64; name resolution on the five cross targets), and nine string-valued builtin expressions gave 3 constant-folded agreeing with the oracle, 6 refusals, 0 silently wrong. Work is parked at devdocs/progress/parked/needsansiruntime-evidence-scan.patch (234 lines, quick gate 35/36 with the one failure attributable and diagnosed above). EARLIER HISTORY BELOW. |
— |
| bug-a-a-plain-frozen-string-records-capacity-zero-so-eleven-clamp-sites-cannot-say-unset | A | 45 | bug | "AllocVar and AllocParam both spell `if TypeIsFrozenString(tk) and (tk | decide-a-what-is-a-plain-frozen-strings-capacity-255-or-eight-megabytes |
| bug-a-a-plain-type-alias-gets-its-own-rtti-blob-so-typeinfo-pointer-dispatch-misses | A | 30 | bug | WIDENED 2026-09-06 BY CENSUS at 0e3558e5a4d4: pxx has NO alias identity mechanism at all. Filed as type TMyInt = Integer minting its own RTTI blob; measured across six spellings, pxx answers DIFFER on EVERY row -- plain scalar alias, synonym (LongInt vs Integer), alias of an ENUM, of a STRING and of a RECORD -- where fpc 3.2.2 answers SAME on all five. Every named type mints its own descriptor. THE ONE ROW PXX GETS RIGHT (= type Integer, which SHOULD differ) IT GETS RIGHT BY HAVING NO OPINION, so it is the obvious control, it passes today, and it passes after a wrong fix too -- do not use it as the control; the five SAME rows discriminate. Breaks the standard RTTI dispatch idiom if p = TypeInfo(Integer): a variable declared through an alias matches NOTHING while name and kind both say it should. SELF-INCONSISTENT, NO ORACLE NEEDED. TWO FURTHER DEFECTS FROM THE SAME CENSUS: (1) an alias of an ENUM yields an INTEGER header (Kind=1, name "Integer") instead of the enum blob, so GetEnumName gets a header where its PEnumRTTI signature says blob -- a second live instance of bug-a-typeinfo-does-not-return-one-shape-of-pointer, reached by an ordinary alias rather than a subrange; (2) an alias of a RECORD yields Kind=13 and SEGFAULTS on its own NamePtr. NOT frankS's variable path, which is correct. CORRECTS A PREMISE IN decide-typeinfo-scalar-name-spelling (the label is identical and the behaviour differs, exactly inverting its dismissal of option 3); that decision is about the NAME STRING and is NOT reopened. |
— |
| bug-a-a-qword-boxes-as-vtint64-so-array-of-const-loses-unsignedness | A | 30 | bug | A QWord passed to array of const boxes as vtInt64 (16); FPC boxes the same source as vtQWord (17). Measured side by side. vtQWord IS declared in builtinheap.pas and appears in EXACTLY ONE place in the whole tree -- its own declaration: zero producers, zero readers. So any consumer that dispatches on the tag the way FPC's does takes the signed branch and renders a value >= 2^63 negative. NOT fixable inside a quick gate: the emit change lands on four backend asm-text readers (x86-64/i386/arm32/riscv32) that test <> vtInteger and <> vtInt64, and three of those are invisible on this host. SUPERSEDED 2026-09-06 (frankA) AND THE PREMISE IS FALSE: d210325a6 landed the EMIT half, so there has been a producer and no readers since -- a WORSE state than filed, because an unlisted tag falls to a case else and rendered the EMPTY STRING rather than a signed value. That is test-core#test_libwriteln_parity, red from d210325a6 until f4b288b16, which adds the vtQWord arm to all four lib readers (libwriteln VarRecToText; sysutils FmtArgStr/FmtArgInt/FmtArgFloat -- %d stays SIGNED on purpose, matching fpc, measured). THE ASM-TEXT BLOCKER IS CENSUSED AND EMPTY: it is SIX readers not four (aarch64 and xtensa postdate this ticket) and NONE is reachable -- all 363 EmitAsm* call sites parsed by bracket matching, every hole argument an Integer literal, constant or field read. They fail LOUDLY if ever reached, never mis-render, so they are a latent trap and are deliberately NOT changed. Beware the obvious census: a line-oriented grep sees only 237 of the 363 sites because 126 calls span lines, and reports nothing for them exactly as it does for a clean site. |
— |
| bug-a-a-record-mixing-an-arc-field-with-a-copy-operator-field-skips-the-operator | A | 35 | bug | MEASURED 2026-09-07. A record holding an ARC field (AnsiString, dynamic array, COM interface) ALONGSIDE a field whose type declares class operator Copy does not run that operator on assignment -- it takes the fused IR_COPY_REC_MANAGED byte copy instead. TMixed = record s: AnsiString; a: TA; k: Integer with TA declaring Copy: fpc 3.2.2 prints A.Copy src=42 dst-on-entry=99 and leaves m2.a.id=99; pxx prints nothing and leaves m2.a.id=42. THIS IS A DELIBERATE REFUSAL, NOT AN OVERSIGHT: IRRecCopyWithHoles and IRArrayElemCopyOps both exit on RecordHasManagedFields, because the hole-punching they do needs the copy expressed as BYTE RANGES and IR_COPY_REC_MANAGED is a single fused retain-release-copy op implemented on six backends that takes a RECORD ID and walks that record's layout descriptor. It cannot be handed a range. THE REFUSAL IS THE OUTER RECORD'S ANSWER and RecordHasManagedFields recurses, so a record whose only managed member sits INSIDE the operator field is refused too -- conservative in the direction that preserves today's behaviour. TWO ROUTES OUT, neither costed: a second descriptor kind describing a SUBRANGE of a record, or an unrolled per-field ARC sequence at the IR level for the mixed case only. The non-ARC halves are fixed and pinned (test_mgmt_operators_copy_contained, test_mgmt_operators_copy_array); this is what is left of bug-a-a-whole-record-assignment-does-not-run-a-contained-fields-copy-operator. |
— |
| bug-a-a-record-parameters-type-is-not-resolved-when-its-slot-is-sized | A | 40 | bug | AllocParam decides a by-value record parameter's slot size from RecSize(LastTypeRecId), and LastTypeRecId is REC_NONE for 41 of the 52 record parameters in compiler.pas. RecSize(REC_NONE) is the 8-byte fallback, so the RecSize(..) <= 8 test that chooses between an inline record slot and a pointer slot is a CONSTANT TRUE for those 41 — the branch's comment describes a decision it is not making. Not a miscompile: every later answer is <= the 8 it reserves, so the slot is over-allocated by up to 4 bytes on a 32-bit target and never under-read. What it costs is that the rule cannot be reasoned about, and it is the input half of the ticket that renamed ParamSize. |
— |
| bug-a-a-static-array-assigned-to-a-dynamic-array-stores-its-address | A | 30 | bug | d := s for d: array of LongInt and s: array[0..2] of LongInt stored the STATIC array's ADDRESS into the dynamic array's handle slot, so Length read the words in front of s as a managed-block header — measured Length(d) = 4310328 and a SEGFAULT walking it, where fpc prints len=3: 2 4 6. The kind check cannot see it: an array symbol's TypeKind is its ELEMENT's kind, so both sides are tyInteger and AssignKindsIncompatible CERTIFIES the pair rather than merely missing it. FIXED: the source is materialised through the array constructor d := [1, 2, 3] already uses, so the elements go through the normal element-assign path with coercion and managed-element ARC. Every lo=0 row diffed against fpc 3.2.2. Also closes tarray12 (its eleventh check, Insert(t3, t, 2) with a static source, through the same call). RESIDUAL, refused rather than silently wrong and now smaller: a dyn-array element, and a source deeper than the destination's row shape. The fixed-ROW destination is CLOSED — it needed the source's leading subscript scaled by BuildPartialNDRowIndex, because array[0..1] of TRow is FLATTENED to 2-D and its ArrLen is the six elements, not the two rows. |
— |
| bug-a-a-static-nilpy-program-links-the-runtime-eval-interpreter | A+N | 60→70 | bug | MEASURED, not estimated: after --dce, compiler/builtin/pyeval.pas is 624,684 B of a riscv32 ESP image's 2,074,812 (30.1%) and 681,868 B of xtensa's 1,736,175 (39.3%), for a program that never calls eval() or exec(). pyeval is a runtime tree-walking interpreter written for uforth's PYTHON-bodied words, and its own header says NOT auto-used by NilPy yet. Its single largest routine, PyHostCall, is 109,396 B by itself -- 5.3% of the whole image in one body. DCE drops only 13.7% of the unit (723,548 -> 624,684) against 30.7% of the image overall, so something ROOTS most of it rather than calling it: the suspects are the @proc / VMT / RTTI root classes (an address in a table is reachable from anywhere by construction) and pylib's hook variables that pyeval installs into. ANSWERED 2026-09-20 by --dce-why, built for this ticket: on xtensa the ENTIRE eval tree hangs off ONE @proc-taken root -- PyHostCall <- PyFieldGet <- DoAssignment <- ExecStatement <- ExecSuite <- CallUserFn <- PyBodyTramp <- [@proc taken] -- so the mechanism is a trampoline whose ADDRESS is in a table, not a call from NilPy code; 4 @proc roots hold 17,420 B directly and drag the rest through ordinary call edges. On riscv32 the same tree is rooted EARLIER and more coarsely: 819,480 B across 128 bodies are held because a stub target lands INSIDE them (143 stub targets, 139 inside a body, against xtensa's 4 and none), so that ISA cannot even see the @proc chain. PRICED 2026-09-20 and the @proc was a DECOY: the real root is pyeval's initialization doing PyIterCallHook := @PyCallKey1, an address that IS written by code that always runs, and PyCallKey1 reaches the interpreter through ONE arm -- pyclosure_call1, whose PyClosureInvoke saves the interpreter's own state and runs a body through ExecStatement. Removing that single arm drops xtensa live code 1,721,914 -> 824,155 B (-52%); removing the whole initialization drops it to 712,617 (-59%). riscv32 shows ZERO for both, because its stub-target rule roots the same bodies independently. The design is to route that arm through a hook installed by exec()/eval() -- the only two entries that can mint a closure -- so the pass can SEE that a program with no eval cannot reach it. Rung of umbrella-an-esp32-image-is-as-small-as-it-can-be: it is the single largest identified component of an ESP NilPy image after DCE. |
— |
| bug-a-a-threadvar-read-in-a-child-thread-faults-once-the-programs-globals-cross-a-size-boundary | A | 60 | bug | MEASURED 2026-09-09 at compiler 470240dd0eb5. Add ONE unused Integer to the program var block of test/test_a_threadvar_is_per_thread.pas and it SEGFAULTS -- in the child thread, at the first read of the threadvar, before any output. The file is green at HEAD, so the shipped test sits one variable from a crash. The fault address is 0xffffffffe03fad30: a 32-bit value SIGN-EXTENDED and used as a pointer, which is why the main thread never faults (its block is low) and a child does (its block is carved off a thread stack, which is mapped high with bit 31 set). The two builds do not differ by an offset -- they emit STRUCTURALLY DIFFERENT code for the same source: with the smaller var block the parameter read is mov -0x8(%rbp),%rax, with the larger one the same source position emits mov %gs:0x0,%rax; mov (%rax),%rax. NO PINNED CONTROL EXISTS: the pin refuses program-level threadvar outright (expected 'begin' before 'threadvar'), so this cannot be bisected against v407 and is young rather than long-standing. Found while repairing the flaky race control in that same file -- the repair needs one more loop variable, which is exactly what trips this, so [[regression-test-threads-test-a-threadvar-is-per-thread]] is blocked on this. |
— |
| bug-a-a-wasm32-program-whose-entry-lowers-to-unreachable-builds-green-and-traps-on-the-first-instruction | A | 55 | bug | A variadic CALL on wasm32 writes a valid 117KB module, prints ok: and exits 0 with main lowered to unreachable — a GREEN BUILD OF A PROGRAM THAT TRAPS ON ITS FIRST INSTRUCTION. The unreachable floor is the wasm backend's own partial-lowering instrument and is correct as a floor; what is unowned is its EXIT-CODE POLICY. Blast radius measured, not estimated: re-measured at origin 54b2cf4d9 / compiler 8f8a089c6812 over a STATED population of 43 sources (test/wasm/*.pas + test/*wasm*.pas): 43 reached the backend, 42 clean, 0 invalid, and EXACTLY ONE emits a gap — the test that exists to document the mechanism, whose unreachable body is main$0, so a naive 'fatal when the entry traps' rule would red that test. So this is not a one-line policy flip and that is why it is filed rather than fixed. |
— |
| bug-a-a-wide-unsigned-literal-boxed-into-a-variant-stores-the-wrapped-value | A | 40 | bug | A wide unsigned literal boxed into a Variant stores the wrapped value | — |
| bug-a-aarch64-has-no-stack-argument-passing-for-the-three-c-abi-call-kinds | A | 30 | bug | TWO OF THE THREE KINDS LANDED 2026-08-31; the CDECL INDIRECT call is what is left. The external-direct call and the variadic external call now place arguments past the banks in an AAPCS64 stack area, and so does the callee (EmitParamSpillsForTarget's ProcCdecl arm, which had the same refusal). All of them ask ONE oracle -- ABIA64CdeclArgSlot in abi.inc -- instead of each counting its own lo/hi, which is what let the register half be right and the stack half be absent three times over. Measured against gcc on a 12-argument mixed int/double signature and a 10-double one, both banks overflowing: pxx now matches gcc exactly on x86-64, aarch64, arm32, i386 and riscv32. STILL OPEN: ir_codegen_aarch64.inc:3574 refuses a cdecl INDIRECT call past 8 arguments -- a different restructure, because that block has an injected Self and the callee address pushed below arg0. The old summary said 'nothing reaches it today'; that was false and is corrected in the body -- a nine-int C function reaches it, lua/src/lcode.c has one, and it broke lua and sqlite on aarch64 the moment a C function always used the C ABI. | — |
| bug-a-address-of-an-open-array-element-points-at-the-marshalling-temp | A | 55 | bug | TWO CONDITIONS BEFORE ANYONE TOUCHES 633 SITES, AND THEY ARE THE FIRST THING TO READ. (1) SEQUENCING: the decision says not to start this while the phase-4 flip is unreleased -- both serialise the backends. That hold names no event anybody can check and no ticket to point at; establish its state with whoever owns phase-4 before starting, and write the answer here. (2) MEASURE FIRST: the 633-site figure assumes arm A is a wire-format change because [ptr-8] is SHARED with dyn arrays and AnsiString handles -- but passing an open-array PARAMETER as two words AT THE CALL BOUNDARY may not require changing the storage convention those two rely on. Nobody has measured that, and it decides weeks vs days. UNBLOCKED 2026-09-03: decide-should-an-open-array-parameter-become-a-two-word-descriptor is DECIDED -- the owner chose ARM A, "if we need more meta info, use more data fields. hardly a decision." Carry the metadata; do not spend correctness to keep one word. The ticket's own recommendation was arm C and was heard and overruled. THE DEFECT: @a[0] inside a callee does NOT equal the caller's @arr[0] for a var or const open-array parameter whose argument is a STATIC array -- every element type, and for a global, a local and a record field alike; FPC answers TRUE for all of them. Cause is representational: a pxx open-array param is a pointer with its length at [ptr-8], so only an argument already carrying that header can be passed by reference, and FPC passes (ptr, high) as two words and therefore aliases everything. NOT a wrong value -- the temp is a faithful, writable, correctly-strided view whose writes are copied back -- it bites only when an address ESCAPES the call. CORRECTED 2026-09-03 by its own author: a DYNAMIC array argument already aliases correctly (1/1, matching FPC), and a VALUE parameter answers FALSE in FPC TOO, so neither row was ever a divergence. |
decide-should-an-open-array-parameter-become-a-two-word-descriptor |
| bug-a-an-explicit-pal-dir-that-contradicts-the-target-platform-fails-deep-in-the-rtl-with-no-diagnostic | A | 30 | bug | --target=wasm32 -Fulib/rtl/platform/posix compiles the POSIX platform_backend for a WASI target and dies on undefined variable (SYS_getgid) -- a diagnostic naming neither the target nor the search path, twenty lines inside an RTL file the user did not write. The override itself is CORRECT and documented (AddDefaultPasUnitDirs appends the target's PAL after any user -Fu, deliberately, so an explicit -Fu wins); what is missing is any warning that the explicit PAL dir contradicts TargetPlatform, which the compiler already knows. Reachable by copying a working Makefile row: a dozen-plus rows hardcode -Fulib/rtl/platform/posix, correct natively and wrong for every cross target. Cost real triage twice in one evening -- reported as a Text-on-wasm32 backend bug, then retracted as a stale binary, and it was neither. |
— |
| bug-a-an-indexed-shortstring-sysopen-path-segfaults-on-x86-64 | A | 55 | bug | SysOpen(arr[0], 0) where arr: array[0..1] of ShortString SEGFAULTS on x86-64 and answers FALSE for an existing file on riscv32 and xtensa. The parser admits the shape because its guard checks Syms[idx].TypeKind and never Syms[idx].IsArray, so an array-of-frozen-string symbol passes TypeIsFrozenString; every backend then re-derives the address from the SYMBOL and gets the array base rather than the element. A crash on the DEFAULT target, reachable from ordinary source, and pre-existing — not introduced by the riscv32/xtensa fix that found it. |
— |
| bug-a-basic-string-concat-in-a-unit-free-program-is-a-compiler-error | A | 35 | bug | Concatenating two string variables in a .bas program with no USES fails with compiler error: call to a runtime stub that was never emitted. The concat lowering reaches AnsiStrConcatAddr, which is 0 because the emitted AnsiString shims are not there -- and they cannot be, because every shim's body is a builtinheap procedure and BASIC pulls builtinheap only through USES. Present on pinned. The sibling of the PXXStrFromLit hole, one stub family over. |
decide-how-much-string-machinery-the-basic-frontend-gets |
| bug-a-comp-renders-as-an-integer-because-it-aliases-int64 | A | 20 | bug | pxx maps comp to tyInt64 (pasparser_lval.inc:8114), so a Comp variable renders through the INTEGER writer: WriteLn(Co) prints 5 where fpc 3.2.2 prints 5.000000000000000000E+0000, because Comp is a real-valued type there (a 64-bit integer-encoded float). The VALUE is correct and now matches fpc on every row — that was bug-a-a-float-assigned-to-an-integer-lvalue-moves-the-bits-instead-of-converting, fixed in 3e2bca576. Only the rendering differs, and it differs on source someone meant to write. NOT settled which way this should go: see the fork below. |
— |
| bug-a-compiler-emitted-runtime-stubs-are-invisible-to-every-gate-we-run | A | 55 | bug | A compiler-emitted runtime stub that compiler.pas never causes to be emitted is invisible to the self-host fixedpoint AND to gate.sh quick, and the blindness is structural rather than a coverage gap to be topped up: the fixedpoint's discriminating power is exactly the set of constructs the compiler writes about itself, and --threadsafe is not one of them. Demonstrated with a dated casualty rather than argued -- 12d6c86f0 fixed a p70 heap-corruption regression (a signal handler granted the heap lock on a bare tid match, then allocating inside a half-updated heap) that had shipped for three days while the BROKEN and the FIXED compiler both printed 'converged after 1 round(s)'. The condition that springs it is any codegen whose output compiler.pas does not itself contain: the heap-lock stubs, the signal runtime, the div0 stub, the float-error hook, anything behind --threadsafe / --fpc-float-errors / --no-signals. THIS IS THE PROBE RULE, NOT A CASE FOR A WIDER GATE -- a valid pin is the fixedpoint and nothing else may block one; CLAUDE.md's existing remedy, 'carry a one-line probe in the affected shape', was simply never applied here, and a --threadsafe canary is a PROBE that blocks nothing. Wants a deterministic BYTES-level assertion rather than another race-dependent runtime test -- the existing test_threadsafe_heap_lock_deadlock_diag does catch this defect but only by winning a race, which is what made it read as a flake for three days. | — |
| bug-a-emit-obj-retains-pxxassert-so-one-ansistring-in-it-imports-the-whole-esp-pal | A+S | 65→70 | bug | UPDATE 2026-09-21 (frankb-8e): ITEM (1)'s BLOCKER IS FIXED AND ITEM (1) IS STILL BLOCKED, ON A DIFFERENT AND WORSE WALL. The i386 two-object crash was a pre-existing bug in DceRun's compaction (ProcAddrFix/DynCall lost their parallel PCRel/PicDelta arrays); fixed at 30898eef7, gate quick GREEN, and guarded by a new fnp_386_dce row. The default was then turned on and make test-emit-obj run: it now dies later, at the ESP IRAM arm, where --dce --emit-obj --platform=esp on an IRAM-attributed routine emits an object that makes GNU LD ITSELF SEGFAULT, on BOTH riscv32 and xtensa, while the ro and --no-ro-data variants link cleanly on both. Pre-existing (pin v415 reproduces it) and reachable with an explicit --dce, so not a property of the default. Default REVERTED again and the reason is now recorded at the site in compiler.pas rather than only here. Filed as bug-a-dce-under-emit-obj-emits-an-esp-iram-object-that-segfaults-the-linker and wired as this ticket's blocker. NOTE the symbol-surface measurement quoted below in favour of the default was taken on objects with NO IRAM section, so it does not cover the failing case. THE LINK FAILURE IS FIXED BY --dce, MEASURED 2026-09-18 (frankB): with DCE now running on xtensa, this ticket's own repro — link the object against the deliberately-no-ESP-IDF shim — goes from ld rc=1 with 24 undefined references to rc=0 with 0, producing a 258556 B ELF; PalBackend 114 -> 0, object 381528 B -> 52476 B, windowed likewise. The three-backend platform_net split (S/B) is NOT required for this bug. TWO THINGS STILL OPEN AND THEY ARE BOTH SMALL: (1) --dce is not on by default, so a default --emit-obj object still over-imports — ANSWERED 2026-09-19 (frankS) AND IT IS NOT A GOAL QUESTION AFTER ALL: nothing competes -- the pass roots an object at its exports via the writer's own predicate, ELF locals cannot resolve across objects, and measured on x86-64/riscv32/xtensa ZERO GLOBAL defined symbols are lost while relocations roughly halve. It is blocked on a BUG instead: turned on, make test-emit-obj dies at fnp_386, a TWO-OBJECT i386 link that links cleanly and then crashes before main (rc=138, no output) where --no-dce prints 20 11. Reproduces with an explicit --dce, so pre-existing and not a regression; default REVERTED and filed as bug-a-dce-under-emit-obj-crashes-a-two-object-i386-link-before-main. The same pass DID land on by default for --esp-profile=bare (e1ffef211), where the evidence is complete; (2) the ratchet this ticket installed counted UND SYMBOL-TABLE ENTRIES, which stay at 20 under --dce because DCE removes code and not symbol entries, while the thing the link actually cares about — RELOCATIONS naming those symbols — goes 24 -> 0; MOVED to relocations 2026-09-18, ratcheted at 24 with the UND count kept as a printed number, because the two fail differently. Original defect unchanged: retention is PER-UNIT (naming platform at all costs all 114) and f0a1a8be9 gave __pxxAssert a string path that reaches the unit. |
bug-a-dce-under-emit-obj-emits-an-esp-iram-object-that-segfaults-the-linker |
| bug-a-fourteen-compiler-internal-record-names-are-reserved-in-every-user-program | A | 45 | bug | IsRecordType (symtab.inc:2385) hard-codes FOURTEEN of the compiler's own descriptor record names -- TToken, TStrEntry, TFixup, TGlobFix, TCallFix, TSymbol, TParam, TProc, TRawToken, TTemplate, TSpecialization, TGenericFunc, TPendingGFSpec, TMethodFixup -- so that compiler.pas's own records carry known ids across the self-host. None of them is reserved in FPC and several are ordinary user type names (TProc, TParam, TToken). A user declaration of one of these wins ONLY if it lands in a table the shadow guard consults: aliases always did, ENUMS did not until 2026-09-06 (fixed; fcl-passrc's TToken = (tkEOF, ...) gave a tyRecord array ELEMENT beside a tyInteger VARIABLE, and pscanner refused three assignments with no position). A user RECORD or an OBJECT of one of these names still loses, silently, and the comparison is case-SENSITIVE (lo = 'TToken') so one program can hold both readings: array of TToken took the builtin while Array[ttoken] of String took the enum. The list is a bootstrap mechanism wearing the shape of a type table; the fix is to scope it to the self-host rather than to add a fifteenth exception per table. |
— |
| bug-a-fourteen-compiler-internal-record-names-shadow-any-user-type | A | 70 | bug | IsRecordType (compiler/symtab.inc:2890) maps FOURTEEN type names to builtin rec ids by string compare, before it ever consults user-declared records. So type TProc = record A: array[0..99] of Int64 end in an ordinary user program silently gets the COMPILER'S OWN TProc layout -- SizeOf 1344 where the declaration says 800 -- with no error and no warning. All fourteen reproduce; two controls (TMyClass, which has a REC_ constant but is not in the chain, and TZZZControl) are correct. TProc and TSymbol are ordinary names in real Pascal (Delphi ships a TProc), so this is a silent wrong-layout bug reachable by correct code that never mentions the compiler. |
— |
| bug-a-hand-written-literal-short-jumps-span-emitters-that-can-grow | A | 35 | bug | Short jumps in the backends carry a hand-counted literal displacement over a span emitted by other code; when that span changes size the jump stays IN RANGE and lands mid-sequence, so nothing errors. FOUR converted, and TWO were ALREADY WRONG on master -- both silent wrong values on x86-64, fixed at 14bc9d218: Write(s:w) on a ShortString with w <= Length(s) truncated (WriteLn(s:2) for 'abcdef' printed nothing; FPC prints abcdef), and LoadFile into a ShortString returned an EMPTY string for every successful read. Both overshot by exactly 8 bytes. DROPPED 70 -> 35 because the CLASS is now guarded, not because the sweep is finished: gate row 74ed877f2 (tools/rel8_literal_span_check.py) fails any literal displacement spanning non-fixed-size emission, with the pre-fix tree as its positive control, and all 41 remaining sites span EmitB/EmitI32 only. Remaining work is converting those 41 for uniformity -- preventive, low expected yield, and safe to leave. Census settled at 42 (now 41) by a POSITION rule that agreed with the comment rule 108/108; the earlier 'about 25' and 142 both came from a grep matching ModRM bytes. |
— |
| bug-a-help-does-not-advertise-flags-the-compiler-accepts | A | 35 | bug | The tool's own self-description disagrees with the tool: --help advertises 24 long options and the parser accepts 68, so 44 work and are not listed (re-measured 2026-09-11 at 13ae05f85; frankD had 45 on 08-30). THE HEADLINE INSTANCE IS FIXED AND THE CLASS IS NOT -- --strict-fpc, --strict and --strict-visibility were listed by d7021131d on 09-05, while --xtensa-long-calls (08-31) and --no-assertions (09-04) arrived undocumented in BOTH --help and docs/, so 13 are now documented nowhere where 11 were. 45-3+2=44: the gap is static because it is being repaired and refilled at about the same rate, which is the argument for generating --help from the parser table rather than listing flags by hand. The failure mode is not a missing line of text: a reader reasoning from --help concludes the flag DOES NOT EXIST and goes to correct whatever cited it. |
— |
| bug-a-i386-arm32-and-riscv32-leak-more-than-x86-64-in-the-same-variant-string-shapes | A | 4 | bug | i386, arm32 and riscv32 leak MORE than x86-64 and aarch64 in the same variant/string shapes — 3616/3856/364 against 1549 on one program before the temp-ownership fix, and the allocation COUNT differs too (i386 8671 vs 5411), so at least one further scope-exit hole is target-specific | — |
| bug-a-i386-esi-and-edi-are-callee-saved-in-the-abi-and-scratch-in-this-backend | A | 40 | bug | The i386 System V ABI makes eax, ecx and edx the scratch registers and ebx, esi, edi and ebp callee-saved. ir_codegen386.inc treats esi and edi as scratch throughout — mov esi, eax (line 64), test esi, esi, mov [edi], al, inc esi / inc edi in the string paths — and saves neither, while it DOES push and pop ebx explicitly wherever it needs it (lines 472, 486). So a pxx-compiled i386 function preserves ebx and does not preserve esi/edi. Nothing in the busybox corpus is hurt by this, because every translation unit there is pxx-compiled and pxx is self-consistent; it bites only where a pxx-compiled i386 function is called from code built by another compiler, which is the direction --emit-obj exists to make possible. Found while adding the i386 inline-asm register pool (bug-c-inline-asm-is-x86-64-only-so-five-busybox-tus-refuse-on-i386), which matches the backend rather than the document and says so; the pool is not the defect and changing it alone would not fix this. |
— |
| bug-a-i386-has-no-float-writer-helper-so-three-frontends-cannot-print-a-real | A | 40 | bug | compiler error: float writer helper not found on i386, for a program that writes a REAL. The Fortran and Algol skeletons hit it and are refused for i386 because of it, while COMPILING AND RUNNING CLEAN on aarch64, arm32 and riscv32 -- so this is not a 32-bit story and not a width story, it is i386 specifically. The message is internal-fault-shaped, which makes it a defect rather than a refusal: it names a compiler-internal helper, offers the user nothing to do, and appears at the end of a successful parse. Measured 2026-09-07 with the frontends' x86-64-only refusals temporarily lifted; both fixtures print reals. BASIC is the sibling reading -- it writes reals too, has never had a refusal, and works on i386 -- so whatever the working path is, it exists on i386 and these two do not reach it. |
— |
| bug-a-irtoplevelstmt-parameter-is-a-node-index-named-k | A | 20 | bug | ir_codegen.inc:8813 declares IRTopLevelStmt(k: Integer) and its body is case IRKind[k] of, so the parameter is a node index. The name reads as a kind, and passing IRKind[i] compiles cleanly and indexes the IR array with an opcode number — a silently-wrong-value trap with no diagnostic, in a function every backend author will call. Rename plus a one-line comment closes the class. |
— |
| bug-a-max-proc-params-is-coupled-to-a-hardcoded-array-bound-by-a-comment | A | 45 | bug | CORRECTED 2026-09-10, and the correction is the finding: there is no const-expr gap, and there is also no coupling to fix. TProc.Params keeps 32 slots whatever its bound says -- array[0..31], array[0..255] and array[0..MAX_PROC_PARAMS-1] all emit the same code/data/bss/procs with SizeOf(TProc)=1344 -- so writing the constant there only makes the two LOOK coupled. The real blocker is bug-a-fourteen-compiler-internal-record-names-shadow-any-user-type: IsRecordType maps the NAME TProc to a builtin rec id before consulting any declaration, so defs.inc's TProc declaration is documentation and the bound never reaches the field offsets. What landed and stands: the thirteen cparser.inc staging locals and argUndecl now derive (locals DO fold), pptrdims had a genuine 6-element overflow, and the overflow diagnostic no longer says 16 when the limit is 32. MAX_PROC_PARAMS stays 32; raising it is a SIGSEGV at exactly 33 until the layout bug is fixed. |
bug-a-fourteen-compiler-internal-record-names-shadow-any-user-type |
| bug-a-nilpy-a-star-argument-in-a-constructor-call-does-not-parse | A | 40 | bug | C(**d) and C(*lst) on a class with an ordinary __init__ fail with expected expression — on the PINNED compiler too, so this is not a regression. The ctor path in pyparser.inc:45097 builds its own AN_ARG chain and never consults the star-forwarding branch that plain calls use. Routing it there needs the receiver prepended, which PyStarForwardCall's signature does not take. |
— |
| bug-a-nilpy-enumerate-over-str-inline-param-leak | A | 35 | bug | — | |
| bug-a-proc-map-emits-static-addresses-for-a-dynamic-build | A | 30 | bug | --proc-map computes every address as LOAD_ADDR + CODE_OFFSET + BodyAddr, using the STATIC code offset unconditionally. A dynamic build (-dPXX_LIBC_HEAP, --shared) sits at DYNAMIC_CODE_OFFSET, so every PROC line is 0x70 low -- a constant shift over all routines. Measured on the pinned binary. It does not fail; tools/vgsym.py resolves the shifted address to the PRECEDING routine, so the symbolized stack is wrong rather than absent. compiler.pas's own comment already states the limitation; nothing enforces it. | — |
| bug-a-promocore-is-not-the-only-place-that-knows-the-promo-slot-layout | A | 25 | bug | ir.inc:9399 says a promotable-int store's two paths 'both go through promocore.pas, the only place that knows the layout'. x86-64's hand-emitted variant-release blob in ir_codegen.inc reads the payload as a literal [rax+8] at three sites, so it knows the layout too. The values agree today so nothing is broken — but this is the same arm, the same shape and the same file as instance #4 of the audit, where an x86-64 hand-emitted twin of a 'single choke point' silently diverged for two months. | — |
| bug-a-pxx-home-is-advertised-but-not-honoured | A | 35 | bug | --where advertises PXX_HOME as tier 2, overriding the exe-dir defaults, but setting it changes nothing: units still resolve from compiler/../lib/rtl, and even REMOVING a unit from the PXX_HOME tree does not produce 'unit not found'. Found while trying to test a compiler hypothesis against a modified copy of the RTL instead of editing Track B's files. |
— |
| bug-a-pxxcoswitch-and-pxxclone-are-missing-on-riscv32 | A | 25 | bug | HALF CLOSED BY EVENTS, RE-MEASURED 2026-09-19 (frankS) at HEAD. THE COSWITCH HALF IS DONE: __pxxcoswitch(@a, @b) COMPILES on riscv32, rc=0, and has since fc70d0cbe (2026-09-02, riscv32 stackful coroutines) — the unsupported node in IR codegen: coswitch error this ticket is named for no longer happens, so the title and the original summary were both stale for 17 days. THE CLONE HALF IS STILL REFUSED BUT NOT WHERE THIS TICKET SAYS: __pxxclone never reaches a missing codegen arm, because it is stopped one layer up — plain, __pxxclone (thread creation) requires --threadsafe; with --threadsafe, --threadsafe is x86-64/i386/aarch64/arm32 only: the heap/ARC/I-O locks are not implemented on this target yet. So what is missing on riscv32 is the THREADSAFE RUNTIME, not a codegen node, and this ticket is a duplicate view of that gap, and it is the only ticket recording it — kept open and kept at p25 for that reason, with the slug left alone because it is the citation key. NOT A PER-THREAD-STATE BUG: both observables are compile-time refusals reachable with ZERO threads running, which is the discriminator that keeps it out of that group. |
— |
| bug-a-pxxdbg-a-ir-star-silently-skips-a-program-main-body | A | 30 | bug | — | |
| bug-a-riscv32-sa-onstack-has-no-effect-under-qemu | A | 12 | bug | riscv32 registers a signal alt stack correctly — the sigaltstack syscall succeeds and the flags word assembles to $18000004 — but the handler still runs on the FAULTING stack under qemu-riscv32, so a stack-overflow SIGSEGV kills the process. The identical construction works under qemu-i386/arm/aarch64 of the same build, which points at qemu-user rather than at us. Unverifiable without hardware. | — |
| bug-a-riscv32-softfloat-has-no-subnormals | A | 40 | bug | RE-LANED OUT OF float/ 2026-09-10: the MECHANISM is a missing capability in the target soft-float runtime, not an accuracy gap, and CLAUDE.md already excludes this from F (NOT F: a crash, hang, wrong signature ... Rank the mechanism, never the datatype'). Exp(-745) returns 0 where every other target gives a subnormal' is a WRONG ANSWER on a working cross target, and float/ is never scanned by ready/next, so it was unrankable. Dropped the +F tag for the same reason. --- riscv32 flushes subnormals: (1e-320 * 0.5) * 2.0 <> 1e-320, Exp(-745) returns 0 where every other target gives a subnormal, and Ln(5e-324) answers -746.52 instead of -744.44. Identical in both float modes, so it is the target's soft-float runtime, not the math unit. i386, arm32, aarch64 and x86-64 are all correct. |
— |
| bug-a-rv32-has-no-timerfd-settime-and-three-skips-hid-it | A | 65 | bug | FIX IS WRITTEN, VERIFIED, AND PARKED as devdocs/progress/patches/rv32-timerfd-time64-and-three-unskips.patch — held only for the landing quiet period, land it when the tier reports. rv32 is time64-only, so scheduler.pas's timerfd_settime(86) answers -38 ENOSYS and ArmOneShotTimer DISCARDS the rc, returning a valid fd for a timer that was never armed: test/test_timer.pas hangs forever on riscv32 (rc=124, no output) while x86-64 prints woke 50/100/150/done. Fourth instance of the class fixed in 677e75495, and the first that is a HANG rather than a wrong value. The row was invisible because the Makefile SKIPPED it as 'backend feature gap' — a reason true when written and stale since rv32 coroutines landed; two sibling skips under the same stale reason (test_reactor, test_asyncecho) PASS today and passed before this fix, so they were pure lost coverage. The two extern_c skips are real and stay. | — |
| bug-a-set-membership-32-bit-backends-truncate-the-set-constant | A | 20 | bug | The 32-bit backends compare the low word plus a fits-in-int32 flag, so a set element above 2^31 truncates: 1 in [4294967297] is TRUE and 4294967297 in [4294967297] is FALSE. x86-64 and aarch64 were fixed in 831919a7d; these three were measured failing the same three rows and are the residual. |
— |
| bug-a-shared-reports-an-internal-error-on-four-targets-where-i386-gets-a-clean-refusal | A | 30 | bug | --shared is x86-64 only and correctly says so on i386. On aarch64, arm32, riscv32 and xtensa the same deliberate limitation surfaces as error: internal: no init/fini thunk prologue for --target=<t> — an internal-error string for a documented, intended refusal. Four of five non-x86-64 targets tell the user the compiler broke; one tells them the truth. Reproduces identically under pin v403 and at HEAD. |
— |
| bug-a-target-enumerations-in-comments-are-stale-and-one-of-them-hid-a-live-bug | A | 20 | bug | Sweep of every comment that ENUMERATES targets, checked against a derived backend list. Three miscounts: PXXVarBinOp's 'the other four targets' (five call it), symtab.inc's 'Every 32-bit backend (i386, arm32, riscv32)' (xtensa is a fourth and does NOT consult the shared decision), and PXXStrCmp3's 'the four cross backends' — that last one already filed as a live bug. A count reads as a complete enumeration, so nobody counts. | — |
| bug-a-test-object-value-type-is-red-on-borg-and-green-on-plexus | A | 55 | bug | test-core#src:test/test_object_value_type.pas has been RED in every native-tier tstate report since 2026-09-17T16:06 (cc03b4a, 19 reports), and the same test at HEAD passes by hand on plexus — rc=0, last line OK. The Makefile row asserts only $(prog | tail -1) against OK (Makefile:20130); it does not read the compiler's ok: line, which appears in the reports solely because twatch's diagnostic extractor picked it as the one line it found. INFERRED, NOT MEASURED: that the program printed nothing on borg — nobody on this fleet can reach borg, so the failing output has never been seen. NOT caused by the ok:-line change (578907347): that commit is NOT an ancestor of ad85bf019, whose report already carries the row red with the OLD spelling code=73496B ... procs=152. Subject is the standard Pascal old-style object value type with methods. |
— |
| bug-a-test-x-on-the-pinned-stable-passes-on-a-foreign-architecture | A | 40 | bug | Five Makefile guards check the pinned stable with test -x, which tests the executable BIT — a property of the file, not of whether this CPU can run it. On a non-x86-64 host every guard reports healthy and the recipe then dies at exec with Exec format error, after printing a message saying there is no pinned stable at a path where one demonstrably is. Found on via (aarch64), where the repo ships only stable_linux_amd64. A reader would reasonably conclude the checkout is broken. |
— |
| bug-a-the-address-of-a-string-element-is-the-literals-address | A | 60 | bug | @s[i] on a string variable assigned a STRING LITERAL answers the LITERAL's address, not the variable's. Three variables assigned 'abcd' — two shortstrings and an AnsiString — all report @x[1] = the same address, where fpc reports three. The VALUES are independently correct (f[1] := 'Z' leaves the others alone), so the storage is separate and only the address is wrong: taking the address of a string element does not uniquify the string. Every consumer that merely READS through it gets the right characters, which is why this has survived; a consumer that WRITES through it corrupts every other variable holding that literal. |
— |
| bug-a-the-basic-frontend-cannot-build-for-riscv32-the-only-driver-with-no-unit-pull | A | 35 | bug | test_basic_comprehensive.bas --target=riscv32 fails with this target has no FPU and the soft-float kernel __pxx_l2d is not linked. BASIC is the only skeleton driver with NO target refusal at all -- it has crossed to i386, aarch64 and arm32 for some time with nothing measuring it -- and the last one still missing the target-runtime unit pull. THE OBVIOUS FIX WAS TRIED AND REVERTED, which is why this is a ticket: adding PullTargetRuntimeUnits before PatchProgramEntryJump (the placement that fixed the other five drivers) made BASIC fail on aarch64 and arm32 with invalid IR symbol reference in load_sym -- it BROKE two targets that worked to fix one that did not. bparser has its own unit mechanism (BSourceUsesAUnit, passed to EmitProgramPrologue) and the two interact; nobody has established how. Reverted rather than shipped, and BASIC is back to exactly its previous three-target state. |
— |
| bug-a-the-clone-stub-registers-a-signal-alt-stack-on-x86-64-only | A | 40 | bug | EnsureCloneStub has four legs -- x86-64, i386, aarch64, arm32 -- and only the x86-64 one carves and registers a per-thread signal alt stack. All five backends DO register one for the installing thread (grepping for syscall 131 finds only x86-64 because the number is per-arch: 186 on i386/arm32, 132 on aarch64/riscv32), so on every non-x86-64 target a cloned thread still has sp=0 flags=SS_DISABLE and its stack overflow is unhandleable. NOT DONE WITH THE x86-64 FIX BECAUSE IT COULD NOT BE VERIFIED: SA_ONSTACK is already known not to take effect under qemu (bug-a-riscv32-sa-onstack-has-no-effect-under-qemu), which is the only way to run these targets here, so the code would have been written and not tested. | — |
| bug-a-the-compiler-prints-ok-with-exact-byte-counts-for-an-output-it-failed-to-write | A | 70 | bug | No write in elfwriter.inc checks its result and sysclose is unchecked too, so on ENOSPC the compiler short-writes the binary and still prints ok: ... [code=NB data=NB bss=NB] with the counts it INTENDED. Measured: a test binary came out 1163348 bytes against a good build's 1585236, 422KB short, reported ok, and segfaulted. It reddened one row of a full NilPy tier as a codecs bug. |
— |
| bug-a-the-compilers-own-source-means-two-different-things-to-fpc-and-to-pxx | A | 45 | bug | compiler/compiler.pas:10 is {$CASESENSITIVE ON}, and fpc has no such directive -- it warns Illegal compiler directive and ignores it. So a pair of identifiers in our own source differing only in case is TWO names to pxx and ONE to the fpc seed, and the seed's local SHADOWS the global. 21 such pairs measured (PXXDBG=a.casedup over compiler.pas, 2026-09-06); pyImportLang in ParseUsesUnitBody is one, and it reads the global on the self-hosted compiler and an uninitialised local on the seed. Bootstrap still converges because compiler.pas contains no NilPy import, so the divergent value is '' either way -- that is luck, not a property. Same class as the seed-drift forward-decl lint the gate already runs, for a VARIABLE rather than a routine. |
— |
| bug-a-the-cross-self-host-proof-runs-a-different-configuration-than-the-native-one | A | 35 | bug | 'x86-64, i386, aarch64 and arm32 self-host byte-identical' is true but is TWO gates, not one: the native proof builds with no flags at all (PXXFLAGS empty) while cross-bootstrap builds with -dPXX_MANAGED_STRING, whose own rule comment says the managed runtime is REQUIRED. Nothing outside Makefile:13809 says so. Track A owns the reason; docs can place the sentence once A supplies it. | — |
| bug-a-the-exception-chain-fix-is-defeated-by-a-libc-pthread | A | 70 | bug | bug-a-the-exception-shadow-chain-is-process-wide-so-two-threads-crash moved TLS_SLOT_EXC_TOP into the per-thread TLS block, and its own note says a fresh thread gets a ZEROED block from the clone stub. A libc pthread never runs that stub, so it INHERITS a chain head pointing at its creator's live frames -- and the fix is defeated for exactly the thread kind DOSBox, SDL and every threaded C library create. MEASURED: main thread and one pthread_create'd thread each doing 300k try/except, 3 runs of 3 print Unhandled exception; the identical 600k of work on ONE thread in the SAME binary is 3 of 3 clean. At 2k each, one run of three produced no output at all. Repro is test/test_foreign_thread_exception_chain.pas, NOT WIRED because it fails -- AND ITS EXIT CODE IS A COIN FLIP AT A FIXED LEVEL WITH A FIXED BINARY, which is the part that decides how to verify a fix: 30 runs at -O2 (2026-09-16) gave 0 x5, 124 x1, 139 x1, 217 x23, and 30 at -O0 gave 139 x3, 217 x27. IT PASSES ABOUT ONE RUN IN SIX AT -O2, so a single-run verification of any fix here reads FIXED on luck at that rate; verify over >=30 runs per level and report the distribution. tools/optdiff.skip carries the file because optdiff enumerates every source file under the test directory and swept a program the suite deliberately excludes -- that skip is on the INSTRUMENT and this bug is untouched and open. |
decide-a-a-foreign-thread-needs-its-own-tls-block-and-the-bounds-are-the-hard-part |
| bug-a-the-heap-arena-reserves-256-mib-without-map-noreserve-so-a-small-guest-cannot-run-any-allocating-pxx-program | A | 65→70 | bug | The first heap allocation in ANY pxx program maps HEAP_ARENA = 268435456 (256 MiB) in one MAP_PRIVATE|MAP_ANONYMOUS request with NO MAP_NORESERVE (builtinheap.pas:885, flags 0x22 at :1231), so a memory-capped guest or container refuses a mapping whose pages would never be touched and the program dies with pxx: out of memory (heap arena mmap failed) before doing any work. IT IS A RESERVATION, NOT USAGE, AND THE GAP IS 650x: a string-concat program peaks at 392 KB RSS and the compiler at 14764 KB compiling it, against 256 MiB demanded. Measured on the beta-0.1 minimal ISO, and it is WORSE than a compiler problem: the pxx-built BUSYBOX hits it too, so at 288 MB ash cannot start and PID 1 dies before /init runs a line -- the floor to BOOT A PXX USERLAND AT ALL and the floor to compile on it are the same floor, bracketed at 288 MB fails / 320 MB works (two independent signals, init banner and busybox's own ash banner; 320/384/448 all pass as the positive control). A program that allocates nothing makes no mmap call at all. This is the ONLY thing standing between the owner's stated beta-0.1 milestone (`a minimal system ... and it all fits in ~64MB of memory') and the actual footprint, which is ~15 MB. |
— |
| bug-a-the-ir-frame-op-doc-asserts-a-frame-layout-riscv32-does-not-use | A | 25 | bug | defs.inc:816 documents IR_FRAME with 'the saved-fp chain IS walkable: [fp] = the caller's fp, [fp + PtrSize] = the return address' — stated as universal. It is false on riscv32, where s0 points at the BOTTOM of the frame and the links sit at +8/+12. ir.inc:4977 knows this and says assuming the common layout 'would have silently walked into the locals'. The lowering is correct (it asks FramePrevFpOffset/FrameRetAddrOffset); the DOC a backend implementer reads is not. | — |
| bug-a-the-no-fpu-diagnostic-advises-uses-softfloat-which-does-not-help | A | 35→40 | bug | The "no FPU" diagnostic tells you to uses softfloat, and doing so changes nothing |
— |
| bug-a-the-pinned-compiler-cannot-build-live-lib-rtl-and-nothing-tracks-it | A | 60 | bug | RECURRED 2026-09-14 WITH A NEW CAUSE, and the 2026-09-06 one below is CLEARED -- pin v408 carried it. Today the pinned compiler cannot PARSE lib/rtl/palthread.pas at all: expected 'begin' before 'weakexternal'. Same documented shape as every previous instance -- a commit added compiler syntax (weakexternal, plus the __pxxTlsBlockSize/__pxxSigAltStackSize builtins) and used it from lib/rtl, which self-hosts and is right, and breaks every $(PXX_STABLE) build until someone pins. Track B and E cannot build lib/rtl until then. ORIGINAL 2026-09-06 TEXT FOLLOWS, kept because the mechanism is the same and the shape is what recurs: The PINNED compiler cannot compile lib/rtl/mimic_string or mimic_urllib_request: undefined variable (pyvar_is_objtag) / (pyvar_is_inttag). A commit added a builtin and used it from lib/rtl without a pin, which is the documented shape. A compiler built from HEAD compiles all 54 units cleanly, so nothing in the tree is broken — the pin is behind the source it has to build. Every $(PXX_STABLE) consumer carries it, so Track B and E build against a compiler that cannot build the RTL they depend on. NOT DISPATCHABLE as coded work: make pin is owner-only and no agent may run it. This ticket exists so the red has an artefact and a date rather than living in whichever session last ran the gate. IT IS NOT ONLY gate.sh quick: Track T's newest FULL tier at b77ac29 (2026-09-06T04:08:52Z) is RED with lib-test#src:tools/crtl_reachability.py failing on those same two identifiers, so the fleet's headline verdict carries it too. The non-ancestry is measured, not inferred: the fix is frankZ's 8374118ec (2026-09-05 23:15) and pin v404 is 8844c8c42 (2026-09-05 20:17), three hours EARLIER -- git merge-base --is-ancestor 8374118ec 8844c8c42 is false, so no pin carries it and no amount of rebuilding at HEAD will change that. |
— |
| bug-a-the-rtti-blob-is-hard-sized-to-64-bit-fields-so-half-of-it-is-padding-on-every-32-bit-target | A | 50 | bug | NOT AN ESP TICKET -- IT IS EVERY 32-BIT TARGET, ALL FIVE: i386, arm32, riscv32, xtensa and wasm32. ESP is where it HURTS (SRAM is the scarce thing there) and not where it LIVES. RTTI_CLS_SIZE = 128 with the comment all fields 8 bytes', and it is the same 128 on every target, so on all five every pointer field carries 4 bytes of padding. SAME CLASS AS [[bug-a-method-pointer-record-is-hard-sized-16-bytes-on-32-bit-targets]], which is DONE and whose own title says "so SizeOf is wrong on all five 32-bit ones" -- a width hard-coded for 64 bits, correct on the host, wrong everywhere else, and structurally invisible because the dev loop, gate.sh quick and the pin all run on x86-64. That one was found by falsifying a wasm32 arm; this one by an ESP SRAM census. Two independent instruments, one class, so the class is live and under-sampled rather than closed. THE DIFFERENTIAL IS MEASURED, not inferred: PXXDBG=a.datamap reports class RTTI headers 10240B' for the NilPy print demo compiled for x86-64 AND for riscv32 --platform=esp, byte-identical, 80 headers either way. On ESP that 10,240 B is SRAM, 8.1% of our 125,832 B, and roughly half of it is padding a 32-bit target cannot use. Pascal VMT slots are the same shape (vmtN * 8' in pasparser_prog.inc) -- 1,848 B here, ~924 of it padding. It is a SIZE bug and not a correctness one: DataPutZeros zeroes the slot and the target is little-endian, so a 4-byte pointer read back as 8 is correctly zero-extended. THE LAYOUT IS SPELLED THREE TIMES and that is the real obstacle -- the emitter (rtti_emit.inc), PXX_RTTI_' in compiler/builtin/builtin.pas, and `RTTI_OFS_' in lib/rtl/rtti.pas -- so narrowing the stride means changing three files that no test forces to agree. Ceiling, not estimate: up to ~5,120 B on this program, less if some fields must stay 8 bytes. |
— |
| bug-a-the-signal-alt-stack-is-32768-bytes-of-unconditional-bss | A | 15→70 | bug | THE SOURCE COMMENT THAT KEPT THIS AN ESP TICKET IS REPAIRED AND THE ESP UMBRELLA EDGE IS CUT (frankh-c0, 2026-09-22). compiler/defs.inc still said EnsureSignalBss allocated the alt stack unconditionally, --no-signals included, so a target whose BSS is SRAM paid 32768 bytes in every image -- true when written, false since 16ebf18ce, and it named a function that no longer holds the allocation. ir_codegen.inc carried the SAME stale referent and was found only by grepping the CONSTANT, not the claim. Re-measured at HEAD by halving SIG_ALTSTACK_SIZE and rebuilding, because a plain-vs---no-signals delta of 0 cannot tell "not allocated" from "allocated on both arms": bare esp32c3 and esp32s3 66808 -> 66808 (delta 0), hosted x86-64 plain 35324 -> 18940 (delta 16384), hosted --no-signals 2532 -> 2532 (delta 0). So it costs an ESP image NOTHING; it was cut from umbrella-an-esp32-image-is-as-small-as-it-can-be, which had been inheriting it to effective 70 and putting it at the top of next --track A while its own author had re-ranked it 15. THE SLUG AND TITLE SAY "unconditional" AND THAT WORD IS FALSE. What remains is PIECE 2 only, hosted-only tuning of a facility that is genuinely live -- the entry installs SIGINT/SIGTERM by default and the emitter registers the buffer with sigaltstack(2). EARLIER: TWO OF THE THREE PIECES ARE DONE AND THE SRAM CASE -- THE WHOLE REASON THIS RANKED -- IS DISCHARGED. Re-measured 2026-09-19 (frankS). This summary described the alt stack as reserved before the if NoSignals then Exit and therefore paid by every image; that stopped being true at 16ebf18ce on 2026-09-18, the body has recorded it as FIXED forty lines down ever since, and the stale summary went on routing this as a live p60 SRAM bug -- which is the exact misroute "a ticket's summary MUST be true" exists to prevent, so it is fixed here rather than appended to. PIECE 1 (conditional on --no-signals): FIXED, 16ebf18ce. PIECE 3 (LINE_BUF_SIZE, 4096 B of stdin line buffer in a program with no ReadLn): FIXED AND NOBODY RECORDED IT -- 0ab100740, the same day, as a side effect of a CORRECTNESS fix ("a long line no longer becomes two"); PXXLineBuf is a Pointer realloc'd on demand and LINE_BUF_SIZE no longer appears in compiler/** at all, which also retires the twin-spelling hazard CLAUDE.md cites against this pair. PIECE 2 (size the constant per target) is what remains and it is hosted-only tuning: after piece 1 the constant costs nothing where no handler exists. MEASURED, hello world, plain vs --no-signals: BOTH BARE ESP PROFILES PAY ZERO (esp32c3 and esp32s3 bare, 66808 -> 66808, delta 0) because TargetHasSignalRuntime is false where there is no OS to deliver a signal; the six hosted profiles pay 32792 = SIG_ALTSTACK_SIZE + the 24-byte stack_t. THAT ATTRIBUTION IS A DIFFERENTIAL AND NOT A MATCHING NUMBER: halving the constant to 16384 moves the delta to 16408 on x86-64 and xtensa-posix and leaves bare at 0, so the source is that constant rather than something the same size. The body's own floor figures are stale too and are corrected below: x86-64 --no-signals bss is 2532, not 9008, and a bare hello is 66808, not 70936 -- both moved by piece 3. Where the cost still lands, it is hosted BSS, which is zero-filled pages with no file cost and where the runtime does install SIGINT/SIGTERM by default, so the alt stack is genuinely used. Re-ranked 60 -> 15 on that: real, correct, and not worth ranker attention. |
— |
| bug-a-the-threadsafe-allocator-is-not-async-signal-safe | A | 40 | bug | A signal handler that ALLOCATES cannot proceed on a --threadsafe program: it re-enters the non-reentrant global heap spinlock the interrupted flow is holding, and that flow is not running. STILL TRUE, STILL NOT FIXED. What changed 2026-09-02 is the OUTCOME: option 3 landed, so instead of hanging with no output at all the program now writes Runtime error 212: the heap lock was never released naming this ticket and exit_group(212)s, measured 1.9s from the collision, 6 runs of 6. The lock itself is unchanged and the fast path is byte-identical; only the contended branch moved out of line into EmitHeapLockSlowStub. Options 1 (block signals around the locked region) and 2 (reentrancy plus instruction-boundary-consistent state) are the actual fix and are untouched, which is why this stays open. The per-thread magazine still narrows the population -- a handler whose traffic fits it never reaches the lock -- so the reproducing configuration is --threadsafe -dPXX_NO_HEAP_MAG with a RETAINING handler. The residual this ticket owns is unchanged: TLS_SLOT_HEAP_MAGBUSY is still reasoned and not verified by execution, because the shapes that would aim a control at it still cannot complete -- they now exit 212 instead of hanging, which makes the aiming failure visible but no less real. |
— |
| bug-a-the-token-pool-stores-text-only-for-identifiers-and-strings | A | 25 | bug | RE-SCOPED 2026-08-30 after an attempt: this is NOT eleven mechanical lexer edits. SOffset/SLen is an OVERLOADED channel, not a text field -- for tkInteger, SLen>0 MEANS 'wider than Int64', so giving ordinary tokens their text makes every integer literal promotable and writeln(42) fails to compile. A correct fix needs a SEPARATE span channel, i.e. new parallel arrays in defs.inc, before any lexer is touched. Original finding stands: every lexer stores token text for tkIdent and tkString only; keywords, punctuation, operators and numbers get SOffset := 0. That if/else is hand-copied across eleven lexers. So the near: window under EVERY diagnostic in the compiler prints the identifiers and silently discards the syntax -- near: begin x >>> end for x := (1 ; -- and no diagnostic can name an offending keyword. Sized: 3.24 MiB of token text against a fixed 8 MiB STRING_CAP, 40.5%, so this is a mechanical change to eleven files, not a pool redesign. |
— |
| bug-a-three-targets-refuse-a-shortstring-sysopen-path-four-implement-it | A | 30 | bug | SysOpen(sp, 0) with sp: ShortString works on x86-64, riscv32 and xtensa, and is refused by name on i386, aarch64 and arm32 (target <arch>: SysOpen expects a managed AnsiString path). One accepted source shape, two answers, split by target. The refusals are now ASSERTED in test-core so the split cannot drift silently — that is the containment, not the fix. Deciding whether to grow the arm needs someone to want it: nothing in the tree passes a ShortString path today. |
— |
| bug-a-two-copies-of-the-wasi-capability-model-one-in-the-pal-one-in-wasibackend | A | 25 | bug | compiler/builtin/wasibackend.pas copied the preopen-resolution and rights logic out of lib/rtl/platform/wasi/platform_backend.pas on purpose, so its landing commit changed no existing file, and said in its own header that the NEXT commit would make the PAL delegate and delete its copy. That commit was never written and no ticket was ever filed. Both copies work, so nothing fails — which is exactly why a capability model is the wrong thing to duplicate: the two drift into one path opening files the other refuses. The unit's self-reporting comment is what caught it. | — |
| bug-a-two-deref-walk-guards-send-a-resolvable-shape-to-the-fallback | A | 40 | bug | ResolveDerefShapeAt has ten typed arms and two fallbacks that now just keep a default. Measured over a full tier with PXXDBG=a.derefwalk, the fallbacks take 1159 hits and only TWO node kinds ever reach them: AN_PTR_CAST 939 times, because the cast arm guards on ASTIVal >= 0 and the adapter casts (ival -1/-2) fall past it; and AN_IDENT 220 times, because the ident arm answers tyUnknown for a pointer whose pointee it cannot name. Both then get tyInteger/tyUnknown, which is a GUESS in a walk whose whole job is to stop guessing -- the same family that produced five wrong-value tickets this week. Not urgent and not known to miscompile anything today: no test fails on it, which is exactly why it needs measuring rather than assuming. The counts come free from the probe, so the first job is to find out whether either default is ever WRONG. |
— |
| bug-a-two-dozen-comments-describe-an-interface-value-as-a-16-byte-fat-pointer-and-it-is-one-pointer | A | 25 | bug | About two dozen comments across compiler/** say an interface VALUE is a 16-byte fat pointer {IMT, instance}. Measured 2026-09-07: it is ONE pointer, the instance, 8 bytes on x86-64 under both pxx and fpc 3.2.2 -- UClsSize_ is TARGET_PTR_SIZE at the interface parse site and AN_INTF_CALL recovers the IMT per call via PXXIntfIMTOf. The AN_INTF_CALL lowering carries BOTH claims in adjacent lines. Not a wrong answer today; it is a wrong map, and it already misled one change. | — |
| bug-a-typeinfo-does-not-return-one-shape-of-pointer | A | 55 | bug | TypeInfo(T) returns two structurally different pointers depending on T. An ENUM resolves to the enum's own RTTI blob (what GetEnumName reads); every other type resolves to a TTypeInfo header (kind, name, DataPtr). Nothing in the type, the operator or typinfo.pas says which one you got, so a routine that reads one shape CRASHES on the other rather than answering wrongly — measured: GetEnumName(TypeInfo(TRange1), 2) on a subrange of an enum segfaulted, walking a member table over a subrange's typedata. That instance is fixed; the two-shapes fact is not, and the next alias or type family that reaches GetEnumName has the same crash waiting. THE Ord(PTypeInfo(TypeInfo(TEnum))^.Kind)-IS-AN-ADDRESS TELL IS RETIRED (frankS, 2026-09-06, on frankA's measurement): it was never the invariant, it was the SYMPTOM of the first instance, and it was written into this summary as though it were the property. It is ONE-DIRECTIONAL and the defect is not -- an enum ALIAS has a plausible small Kind and a record ALIAS has the RIGHT Kind, and a reader following the tell certifies both as clean. A one-directional tell on a two-directional defect is worse than none. THE INVARIANT THAT SURVIVES ALL THREE INSTANCES IS ABOUT THE SHAPE, NOT ANY FIELD: TypeInfo must return ONE kind of pointer, and the check a caller can actually run is does the type I asked about answer the SAME pointer as the type it aliases -- the five SAME rows of the census in bug-a-a-plain-type-alias-gets-its-own-rtti-blob-so-typeinfo-pointer-dispatch-misses, which is this ticket's acceptance test. TWO MORE INSTANCES ADDED 2026-09-06 (frankA), from the alias census: an ordinary ALIAS OF AN ENUM (TMyColour = TColour) yields a HEADER with Kind=1 named "Integer" instead of the enum blob -- the OPPOSITE direction to the subrange instance, so a fix that only detects "blob where header expected" misses it; and an alias of a RECORD yields a correct Kind=13 and SEGFAULTS on its own NamePtr, which the Kind tell cannot detect at all because Kind is right. The standing Ord(Kind)-is-an-address tell covers NEITHER. Meets bug-a-a-plain-type-alias-gets-its-own-rtti-blob-so-typeinfo-pointer-dispatch-misses in one table; fix neither without reading the other. |
— |
| bug-a-tyunknown-is-both-untyped-pointer-and-i-read-garbage | A | 40 | bug | tyUnknown is simultaneously the legitimate 'untyped Pointer' pointee sentinel and the value every unwritten/recycled slot reads back as. A consumer cannot tell 'this parameter genuinely takes anything' from 'I read a slot that is not mine', and because the permissive answer is the shared one, every such guard fails OPEN. | — |
| bug-a-unary-minus-on-a-variant-loses-the-sign-of-zero-because-the-correct-helper-is-not-wired | A | 35 | bug | -v where v is a Variant holding +0.0 answers +0.0; the same expression on a plain Double answers -0.0, and pylib's own pyneg_v(v) -- called by hand on the same Variant -- also answers -0.0. So the correct helper EXISTS and the lowering does not call it: Variant unary minus is evidently going through a subtraction from zero, where IEEE gives 0 - 0 = +0. Measured in Pascal (three rows, one program) and inherited by NilPy, where it splits by expression shape exactly as the Variant boundary predicts: -x on a local or a literal or a self.f or a def f(a: float) parameter is CORRECT, and -lst[0], -tup[0], -dct[k] and -a in an UNTYPED def are all wrong. atan2's quadrant answers depend on it: CPython's math.atan2(-0.0, 1.0) is -0.0 and ours is +0.0 wherever the argument arrives through a Variant. |
— |
| bug-a-wait4-does-not-write-rusage-on-riscv32 | A | 55 | bug | wait4() does not write its rusage out-parameter on riscv32, and does on every other cross target. MEASURED 2026-09-12 on borg, full native tier, job test-core#2020 (test/c_crtl_wait.c): expect_same MISMATCH [riscv32/c_wait26], - wait4-rusage rusage=written / + wait4-rusage rusage=UNTOUCHED. i386, arm32 and aarch64 all OK on the same row, so it is the riscv32 syscall path and not the C test or the crtl shim. NOT environmental — found alongside a multilib fix on that box and explicitly separated from it: the other two rows in that tier went green when multilib landed, this one did not move. One-target width/ABI shape, which is the class x86-64-only development is structurally blind to. No skip was added and nothing was deleted. |
— |
| bug-a-write-picks-a-different-float-width-per-target-and-both-disagree-with-fpc | A | 30 | bug | Write of a real renders at a width that depends on the TARGET: x86-64 prints s1+s2 (Single+Single) in Double form where FPC and xtensa print Single, and xtensa prints i/2 in Single form where FPC and x86-64 print Double. Two backends, opposite errors, same source and same compiler. The values are right; the width dispatch is not. |
— |
| bug-a-xtensa-cannot-lower-a-store-through-a-pointer-so-no-c-program-that-writes-through-a-parameter-compiles | A+S | 60 | bug | char *f(char *d){ *d = 0; return d; } at --target=xtensa --emit-obj gives IR_UNSUPPORTED: frontend could not lower AST node (kind 5) -- AN_BINOP, the C frontend's assignment-expression form. The same source compiles for riscv32, i386 and x86-64. It is the STORE and not the dereference: a pointer READ through the same parameter (char f(char *d){ return *d; }) compiles on xtensa fine, and an int store fails identically, so it is neither width- nor const-specific. The blast radius is the whole crtl: lib/crtl/src/string.c refuses at line 188 (*d = '\\0' in strncat), so ANY xtensa C program that calls a crtl-provided libc routine -- strlen, puts, printf were each measured -- fails at that line while a program calling only its own functions compiles. WHY NOBODY HAS HIT IT: xtensa has no C entry stub (C program entry stub on xtensa: a STANDALONE...), so --emit-obj is the only C route on that target and nothing in the tier drives it with a libc call. Found while trying to give xtensa a probe for tools/reloc_resolve_check.py; the harness reported SKIP with the real diagnostic rather than a pass, which is how it surfaced. |
— |
| bug-a-xtensa-tkill-syscall-number-is-unlocated | A+S | 25 | bug | xtensa's SYS_tkill number is not known, so test_signal_siginfo.pas and test_signal_num.pas still have no xtensa arm and do not build for that target. gettid IS settled (127, by qemu -strace, calibrated against getppid=150), and 224 is sigaltstack on xtensa -- NOT gettid as it is on ARM and i386 -- so copying either of those arms would call the wrong syscall and get a plausible return. tkill is not in 205-215, 227-252, and a blind (0,0) sweep of 118-136 terminated the process without tracing, so that range was abandoned rather than pushed through. Needs a non-sweep source. | — |
| bug-c-generic-selection-loses-an-array-elements-pointer-target-and-its-constness | A | 35 | bug | _Generic over an array controlling expression now decays correctly for scalar, record and multi-dim elements (measured equal to gcc), but two element shapes still select the wrong association: int *p[2] answers default where gcc answers int **, and const int ci[2] answers int * where gcc answers const int *. Both need a carrier the array symbol does not have — the element's POINTER TARGET and the element's CONSTNESS — so neither is fixable at the descriptor site. Two rows of a seven-row gcc differential; the other five match. |
— |
| bug-n-unary-operators-and-abs-on-a-bool-keep-the-bool-tag-so-minus-true-is-true | N | 45 | bug | MECHANISM: a unary operator applied to a VT_BOOL returns the operand's TAG rather than converting to int, so the result re-renders as a bool. It SPRINGS wherever a numeric operation's result is handed back through the operand's own variant tag instead of a numeric one. For NEGATION this is not a display defect but a WRONG VALUE -- -True answers True (i.e. 1) where CPython gives -1, so the sign is discarded silently. Four instances measured 2026-09-22: -True -> True (CPython -1), +True -> True (1), abs(True) -> True (1), abs(False) -> False (0). THE BINARY OPERATORS ARE ALL CORRECT, which is what hides it and what bounds the fix: True+1, True*2, True//1, min(True,5), max(True,0), sum([True,True]), int(True), divmod(True,1), pow(True,1) and round(True) every one matches CPython. So a bool is converted correctly everywhere EXCEPT where the operation is unary. Not caused by the Low(Int64) work landed the same day -- established by stash, rebuild and re-run, where these rows are unchanged and only the Low(Int64) row moved. |
— |
| bug-o-nothing-asserts-that-o2-actually-uses-the-static-literal-handle | A | 35 | bug | EmitStaticLitHandle (compiler/ir_codegen.inc, gated OptLevel < 2) turns a string literal into an address into the image instead of a PXXStrFromLit call that allocates and copies — worth 9.28% of uforth's profile in PXXStrFromLit alone and most of another 19% in the allocator around it. If the pass silently stopped firing, EVERY test would still pass: the literal would become an ordinary refcounted heap copy, which is what -O0/-O1 already produce and what the whole suite already accepts. The optimisation has no guard, only its correctness does. |
— |
| bug-wasm-hosted-compiler-crashes-node-but-not-wasmtime-on-a-full-compile | A | 25 | bug | The residual after the WASI u64-alignment defect was fixed. On the SAME module, wasmtime compiles a program to a byte-identical ELF five times out of five at exit 0; node segfaults five times out of five, leaving a 0-3 byte artifact. Node handles --version, --where and --list-libraries (a directory walk) at exit 0 and dies only on a full compile. A sandboxed guest cannot fault its host, only trap, so this is host-side — and unlike the predecessor ticket that inference now has a control behind it. LOW PRIO: wasmtime is the campaign's host and the milestone is met without node. | — |
| chore-a-adopt-allocrecvar-at-the-twenty-remaining-record-temp-sites | A | 35 | chore | chore(A): adopt AllocRecVar at the 20 remaining AllocVar(…, tyRecord) sites |
— |
| chore-a-decide-whether-widestring-can-come-out-from-behind-pxx-wide-payload | A | 25 | chore | Declaring w: WideString gives UTF-8 bytes by default and UTF-16 units only under {$define PXX_WIDE_PAYLOAD} — a live behavioural difference (Length 5 / w[4]=195 vs 4 / 233 for 'café'). The gate is DELIBERATE, left in place when feature-unicodestring-model closed, and this ticket exists so the next reader can tell that from forgotten. Retiring it is a measurement, not a decision. STEP 1 IS DONE (frankS, 2026-09-07): forcing the define across the conformance corpus gives 406 pass / 5 fail against 411 / 0, all five one mechanism, filed as bug-a-a-case-of-string-on-a-widestring-matches-nothing-under-pxx-wide-payload -- so the gate STAYS behind it. Steps 2 and 4 (lib/, examples/, non-ASCII data) are still unrun and the step-4 fpc oracle does not work as written. |
bug-a-a-case-of-string-on-a-widestring-matches-nothing-under-pxx-wide-payload |
| chore-a-delete-the-dead-pascal-lvalue-statement-path | A | 30 | chore | ParseLValue and CompileLValueAddress in pasparser_lval.inc have no callers anywhere in compiler/** — ~130 lines of pre-AST statement-assignment parsing, including direct machine-code emission, that nothing reaches. |
— |
| chore-a-re-include-bench-timing-in-tools-devtest | A | 30 | chore | One line: tools-devtest skips bench_timing_devtest.py with an explicit case ... continue, added by a1fd5715e because the guard was load-sensitive. It has been fixed (c194b01e9) and is green under load average 14. Deleting the skip re-arms the only guard for bug-t-bench-sub-second-timings-quantized-to-50ms, which has not run in the fleet since the family was wired up. |
— |
| chore-a-retire-the-dead-pyexec-stub-and-its-stale-comments | A | 15 | chore | compiler/builtin/pylib.pas still carries a no-op pyexec stub, plus comments in pylib.pas and pyeval.pas saying things SEGFAULT 'because pyexec is a stub'. Engine 1 landed 2026-07-31 and exec lowers to pyeval's EvalPyStmts — nothing calls the stub. The stale prose is the cost: it reads as an unimplemented feature and made a reader doubt a done, gated one. |
— |
| chore-progress-flag-prose-only-track-decl | A | 25 | chore | progress.sh check should flag a ticket that declares its track only in prose |
— |
| feature-a-a-byte-compare-over-padding-free-runs-would-retire-the-record-compare-unroll-cap | A | 30 | feature | Record = is expanded field-wise and an array member is UNROLLED, one comparison per element, at ~55 bytes of emitted code per leaf (measured x86-64, default -O, flat from 100 to 1280 leaves -- linear, no knee). REC_CMP_UNROLL_MAX = 64 bounds that at ~3.5KB per comparison site, and above the cap the comparison falls back to the pre-2026-09-07 first-word compare and answers WRONG for records differing past the first word -- with a warning, so the cliff is visible, but still wrong. The fix is NOT a bigger cap. An array of a scalar has NO INTERIOR PADDING, which is exactly what makes a byte-wise compare unsafe for a whole record (padding is undefined, measured: field-equal records differ in 7 of 16 bytes) and SAFE over that run: a byte-compare over any contiguous padding-free run is O(1) code with no cap. It needs a PXXMemCmp, which does not exist -- compiler/builtin/builtinheap.pas declares PXXMemMove, PXXMemZero and PXXMemCopy only. Low prio because the population is empty today: the cap warning fires ZERO times across test-core. |
— |
| feature-a-a-refusal-is-a-claim-with-a-date-on-it | A | 35 | feature | — | |
| feature-a-a-table-filled-at-startup-is-sram-that-could-have-been-flash | A | 45 | feature | On ESP-IDF our SRAM is .data + .bss, and .bss is the LARGER half -- 89,352 B of 125,832 on the NilPy print demo, 71%, measured 2026-09-20 with build.sh's sram mode. The read-only-segment work cannot touch .bss by construction: a section with no file contents has nothing to place in flash. But a table that is CONSTANT AFTER INITIALISATION and reached .bss only because it is written by startup code is not really mutable data -- it is a constant paying twice, once in SRAM for the zeroed slot and once in code for the fill loop. Baking such a table into .rodata instead removes both. THE POPULATION IS NOT ESTABLISHED and that is the first task: nothing currently distinguishes filled once at startup and never written again' from written during the run', and a.datamap reports .bss as one unattributed lump. tools/... a.constdata already names the Pascal typed-const arrays that DID get promoted, and its own useful signal is the arrays that did NOT -- promotion is all-or-nothing per array and fails closed -- so the refusal population is a place to start looking, not the answer. NOT on the read-only-segment ticket's REMAINING list, and larger than everything on it: that list's named items measure at 32 bytes on this program. |
— |
| feature-a-a-tagged-transient-region-for-conversions-that-have-no-handle-to-refcount | A | 30 | feature | Owner's direction, restated 2026-09-14 from an early-days exchange: there's no shame in a certain garbage collection ... sometimes it would be easier to just alloc memory, tag it, use it, have it collect after we proven it's no longer in use, and he names the thread-safe PChar conversion as the typical case. The prior answer -- mine -- was not needed, procedure exit cleans up temps, and that was too narrow: IRParkManagedStr IS alloc-tag-use-collect, with a refcount for the tag and lexical scope for the proof. What it cannot serve is a conversion with NO HANDLE to refcount -- one that must BUILD bytes (append a NUL, transcode, format) -- and that case has exactly one answer in the tree today, a file-scope static, which is the bug gtk3's PC() just was. NOT a rewrite of ARC and not a tracing collector: a per-thread bump region, reclaimed at a proven point, for transients at a foreign seam. Thread safety falls out of per-thread rather than being designed in. THE OPEN QUESTION IS THE RECLAIM POINT, not the allocator. |
— |
| feature-a-a-target-generic-resolve-and-compare-harness-for-emit-obj-objects | A | 55 | feature | Every ESP object this tree emits -- riscv32 and xtensa, both with shipped writers -- is verified only by readelf -r assertions on relocation TYPE and SYMBOL, and NOTHING in the tree has ever linked or run one. Type-and-symbol cannot see a wrong addend, a misplaced bit-field inside an instruction's immediate, or an offset four bytes out, which is precisely the defect class an object writer produces. The instrument that closes it needs no linker and no new target support: pxx emits the same program as an EXECUTABLE (proven by running under qemu in the existing per-target tiers) and as an OBJECT, the harness applies the object's relocations at the executable's section addresses USING ITS OWN ARITHMETIC, and the resulting .text and .data must be byte-identical to the executable's. INDEPENDENCE IS THE WHOLE VALUE and is the easy thing to lose: if the harness applies relocations by calling back into pxx, or if the object writer records a value the executable writer already computed instead of an addend the harness must resolve, the comparison is a self-consistency check wearing the shape of a correctness check and it passes forever. Build it target-generic rather than aarch64-shaped -- xtensa and riscv32 inherit it for free and that is the axis the owner tests personally. Positive controls must perturb TYPE, ADDEND and OFFSET independently, and each must redden. Residual it cannot close: whether a real LINKER agrees with our relocation semantics (needs a cross-linker this box does not have), and any relocation on a path qemu never executes. |
— |
| feature-a-a-variant-has-no-null-tag | A | 45 | feature | pxx has one no-value variant tag (VT_EMPTY), so VarIsNull and VarIsEmpty are the same question and v := Null; VarIsEmpty(v) answers True where FPC says False. variants.pas states the approximation in its header and asks for a ticket rather than a silent guess — this is that ticket. A VT_NULL tag is a compiler change, and decide-variant-tag-space-is-a-language-wide-commitment already settled that the tag space is Track A's to renumber freely. |
— |
| feature-a-an-extern-only-variable-still-reserves-its-storage | A | 25→70 | feature | MEASURED 2026-09-18 (frankB) -- THE GATING QUESTION THIS TICKET ASKS IS ANSWERED AND THE ANSWER IS NO. It says to check first whether real inputs make a reclaim pass worth anything; checked, and the cost is ~241 bytes per busybox translation unit (33 imports, 102 bytes of declared size plus 139 of alignment padding, others-in-span=0 so the span is exact) against a 91 KB .bss -- 0.26%, because libbb.h's names are almost all extern const char x[], an INCOMPLETE array reserving one byte, so the cost is alignment on 1-byte slots rather than the declarations' sizes. ZERO on ESP, by construction, on both profiles: an --emit-obj xtensa/riscv32 object refuses an imported variable outright (ObjRefuseEspDataImports) and an executable refuses it on any target, measured to 0 imports on a real ESP object, with lib/** declaring no external variable at all -- so this ticket must NOT be raised under the ESP SRAM re-rank. Nothing could measure this before: the UND symbol carries st_size 0 and NOTYPE, so readelf cannot see the reservation and the size lives only in SymAllocSize. PXXDBG=a.impwaste now reports it at write time through the same ObjDataIsImport predicate the writers walk, in two bounds (sum of sizes is a lower bound; the offset span is an upper bound and only exact when others-in-span=0), with a positive control on this ticket's own fixture -- 4000 against 0, identical bss. LEFT AT PRIO 25 AND NOT IMPLEMENTED: the reclaim would have to remap every GlobFix.BSSoff, which stores an OFFSET and not a symbol index, making it a DceNewOff-shaped compaction with that class of risk, for 241 bytes per object. Original defect unchanged: AllocFromDeclTypeDesc reserves at declaration time and the import decision is a fold that is not final until the unit ends. |
— |
| feature-a-coswitch-for-xtensa-and-riscv32-the-scheduler-has-no-context-switch-there | A+S | 30 | feature | EmitCoroutineRuntime covers x86-64/i386/aarch64/arm32 and refuses wasm32; xtensa and riscv32 fall through SILENTLY by design, so CoSwitchAddr is never set. Today that is harmless because the scheduler cannot compile for either target anyway — it has no syscall block for them. The moment either gets one, six programs stop erroring and start jumping into code that was never emitted, onto a stack primed with the x86-64 frame layout. Found while filling xtensa's syscall table; the numbers were deliberately NOT added for this reason. | — |
| feature-a-crtl-is-not-large-file-safe-at-ilp32 | A | 45 | feature | crtl is not large-file safe at ILP32: off_t is 32 bits, so >2GB files are wrong | — |
| feature-a-dce-can-drop-a-body-whose-only-stub-target-is-a-thunk-that-body-owns | A | 40 | feature | DceRangeHoldsStub roots any body whose address range contains a CodeRef target, which is correct for a target something EXTERNAL jumps into and over-approximate for the commonest case: a managed-local sweep thunk, which is called only by the body it sits in. Measured 2026-09-20 on examples/esp32/nilpy-c3 (riscv32, --platform=esp --dce): 128 bodies and 819,480 B -- 39.6% of live code -- are rooted holds a stub target, and every one of those targets is a sweep thunk placed by EmitProcScopeExitCleanupForTarget after the body's second return. The pass already has what it needs to tell the two apart: DceOwnerOf(target) names the body the target is inside, and EmitSweepThunkCall's call site is inside that same body, so an OWNED target could be treated as an ordinary intra-body reference and the body dropped when its owner is dead. That would make riscv32's live set match windowed xtensa's WITHOUT disabling the thunk -- xtensa is only smaller because TargetHasSweepThunk excludes the windowed ABI, which is a trade, where this is not. The root rule must STAY for genuinely unowned targets; the change is to stop applying it where the owner is also the only caller. |
— |
| feature-a-emit-eh-frame-so-an-external-profiler-can-unwind-past-the-leaf-frame | A | 50 | feature | pxx emits no .eh_frame (the string appears nowhere in compiler/), so gdb and any sampling profiler can report the LEAF function and nothing above it. Leaf profiling works today via the .map; inclusive profiling is unavailable. |
— |
| feature-a-emit-obj-record-class-abi-mode | A | 40 | feature | --emit-obj objects built with and without --compact-classes disagree on VMT slot numbers, and nothing diagnoses it. Record the class-ABI mode in the object and refuse a mismatched link, the way --threadsafe's hazard is meant to be handled. | — |
| feature-a-error-does-not-halt-so-a-parse-can-be-speculative | A | 35 | feature | Error() calls Halt directly, so nothing in the compiler can trial-parse and back out. That blocks NilPy's type inference (which needs to read an as-yet-unseen name speculatively), and it is also why the compiler stops at the FIRST error. Make the error path recoverable; several unrelated wants fall out of the same change. |
— |
| feature-a-finalize-for-bare-dynarray-and-variant | A | 30 | feature | The VARIANT half landed 2026-08-24 (PXXVarClear through the slot address, FPC-identical on four targets); what remains is the DYNAMIC ARRAY only. A bare dyn-array lvalue still gets a clear compile error, because the release helper needs a per-symbol element descriptor that has no IR-level dataref sentinel (SYM_RTTI_DATAREF_BASE is declared but has no fixup branch). A record CONTAINING one is fully handled, and a := nil is the one-line workaround, so the gap is narrow. |
— |
| feature-a-getinterface-refcounting | A | 45 | feature | __pxxGetInterface stores the instance pointer into the caller's interface variable without an AddRef, so the slot holds a borrowed reference while the compiler treats the variable as managed and releases it at scope exit. Every Supports/GetInterface hit is therefore one release the object never got a retain for. Nothing observed to crash yet, which is why it is a ticket and not an urgent bug — but the asymmetry is real and worth settling deliberately. | — |
| feature-a-make-the-heap-lock-reentrant | A | 60 | feature | DECIDED 2026-09-06 (decide-a-how-should-the-nilpy-managed-finalize-re-enter-the-heap-lock, arm (a)) and LANDED THE SAME DAY in three steps. The reentrancy half of feature-a-reentrant-heap-lock-and-per-thread-arenas -- parked by the owner 2026-08-21 with an explicit unpark trigger, 'a deadlock, or a new managed member kind whose release cannot be hoisted out of the lock' -- is unparked because a deadlock arrived. BSS_HEAP_OWNER/BSS_HEAP_DEPTH sit ON TOP of BSS_HEAP_LOCK, so the contended path and its exit-212 diagnosis are untouched; identity is EmitIoLockStubs' sequence (cached TLS tid, trusted only when rsp is inside that block's recorded stack bounds, else gettid), because gs:[0] is INHERITED across clone and recording the tid harder at thread start cannot help a thread that has no thread-start of ours. IT CLOSES TWO ROWS, MEASURED WITH A DISCRIMINATING CONTROL: the threadsafe dyn-array Variant/interface leak goes 7939 -> 3 live at the SAME allocation count, and the same program under -dPXX_NO_REENTRANT_HEAPLOCK hits rc=212 with the heap-lock diagnosis -- so the reentrancy is load-bearing rather than assumed; and the NilPy threadsafe class-field leak goes 19760 kB -> 1048 kB maxrss. THE COST OBJECTION HAD SUBSTANCE AND THIS SUMMARY USED TO SAY IT WAS NEVER MEASURED: it is +7% with the magazine on and +14% off, 1M construct/free iterations, min-of-5 interleaved, allocation-saturated upper bound on a shared box. Fork NOT re-opened -- the owner ruled with the objection in front of him. Narrowing avenue if it ever matters: only acquires inside a HeapLockedCallProcIdx1 region can nest, so every other site could keep the inline TTAS. STEP 2 SHIPPED A RACE AT 3bb71fd79 AND IT IS FIXED: the stamp was moved into EmitHeapLockStubs, which runs from the PROLOGUE before builtinheap.pas is parsed, so FindProc answered -1, the if >= 0 guard silently did nothing, and the managed walk emitted with NO lock -- both test_threadsafe_class_finalize_* rows segfaulted 30/30. Resolution is at the CALL SITE now (Procs[procIdx].Name against the callee, which cannot answer -1 about a proc it is looking straight at); 30/30 green both rows, acceptance numbers unchanged. THE LESSON IS THE ASSERTION CLASS: restoring the CALL fixes the leak and the LOCK is a separate property, so every leak- and maxrss-shaped instrument I had improved identically with the acquire missing -- which the race test's own header had predicted in writing a week earlier. |
— |
| feature-a-merge-the-wasm-branch-the-shared-file-arms | A | 20 | feature | Branch wasm modifies four existing files: compiler.pas (5 edits), exception_emit.inc (1 arm), ir_codegen.inc (1 arm), and lib/rtl/platform.pas (1 additive constant). The last is Track B and carries B's gate, so the merge review spans two lanes, not one. Nothing on the branch is pre-approved. This ticket is the ledger; the branch's own CHARTER table is not visible from master and was stale. |
— |
| feature-a-o-the-refcount-lock-is-still-global-but-nobody-has-measured-that-it-costs | A | 25 | feature | The managed-refcount critical sections still take the ONE global heap lock — 10 emitter sites (EmitManagedLocalCleanupForTarget 5, EmitDynArrayRetain/ReleaseForSym/ReleaseForNode 3, symtab.inc 2) plus EmitAnsiStringRuntime's 7 mixed ones — so threaded code that retains and releases managed values serialises on the allocator's lock even though it never allocates. This is step 1 of the old feature-threadsafe-heap-optimize plan, which that ticket went AROUND rather than through: the owner's per-thread magazine (250fdc6bd) bypassed the pure alloc/free sites and flattened the scaling curve without touching these. PROMISE IS UNMEASURED and that is why the prio is low: the only benchmark we have (bench/threadsafe_heap_scaling.pas) is alloc-heavy and the magazine already flattened it, so NOTHING here is a demonstrated cost — it is an instruction census, which the O-lane rule says is not a promise. The first job is the benchmark, not the fix. The shape of the fix is known and is a DELETION: give the refcount role atomic primitives and remove it from the lock, rather than adding a lock order. | — |
| feature-a-one-argv-to-frozen-filler-instead-of-x86-64s-inline-copy | A | 30 | feature | argv -> frozen string is implemented TWICE: five backends call the RTL's PXXCStrToFrozen, and x86-64's EmitArgvToString open-codes the same contract as emitted bytes (its own strlen, its own cmp against FROZEN_CSTR_CAP, its own rep movsb). They agree on 255 today, and the agreement is maintained by hand. Normalising means deleting the inline copy and making x86-64 call what the other five already call -- no observable behaviour change. Filed rather than bundled into the crash fix that measured it. | — |
| feature-a-one-guard-excludes-both-the-unimplementable-and-the-merely-adjacent | A | 35 | feature | builtinheap.pas gates whole REGIONS of the unit on a profile marker, so a routine is excluded from the bare ESP profile by where it sits in the file rather than by what it needs. Each {$ifndef PXX_ESP} / {$ifndef PXX_ESP_BARE} block contains a small genuine core -- a filesystem open, syscall stdio -- and a large remainder of pure pointer/memory code that would compile anywhere; ~25 routines (the managed record and dynarray retain/release walks, the variant runtime, PXXCStrToFrozen) are lost to bare for adjacency alone. The mechanism is a POSITIONAL guard standing in for a capability one, and it springs whenever a new routine is added inside an existing block: it inherits that block's exclusions silently and no diagnostic names the reason. The unit's own header already states the principle it violates -- none of these bodies is unimplementable on an ESP chip. PARTLY LANDED 2026-09-20: the record and dynarray walks and the class finalizer are no longer profile-gated; the arms inside them that touch a COM interface, a variant or a NilPy promo field still are, because those SURFACES are genuinely excluded on ESP while the walk that visits them is not. Managed records now compile AND RUN on bare metal -- test/test_esp_bare_managed.pas boots under qemu on esp32c3 and esp32s3 and matches its x86-64 oracle byte-for-byte. STILL POSITIONAL AND STILL TO DO: the variant runtime and the float-formatting routines, which share the 5649-6940 span with each other; the float half additionally needs the on-demand softfloat pull that bare deliberately skips, so it is not the same change. | — |
| feature-a-promoint-variant-esp-targets | A+S | 20 | feature | Promotable int in a Variant: riscv32 / xtensa | — |
| feature-a-report-fixed-cap-headroom | A | 40 | feature | Three fixed caps in defs.inc have now been raised AFTER a user hit them — MAX_CODE 8->16 MB, MAX_STRS 8192->65536, MAX_CODE 16->32 MB — and each was found by a program failing, never by anyone looking. Nothing reports how close a compile came to any cap, so the only headroom signal the project has is an overflow. Proposal: a PXXDBG=a.caps topic printing per-cap utilisation at end of compile, so the next one is a number someone can read instead of an incident. Small, additive, no behaviour change. |
— |
| feature-a-shrink-managed-header-on-32-bit | A | 10 | feature | On ILP32 the managed-block header wastes 12 of its 24 bytes: three 8-byte slots each carrying a 4-byte value. Packing to 4-byte slots halves it — and the DEADLINE is phase 2, because it caps the meta word at 32 usable bits | — |
| feature-a-stamp-and-read-the-managed-string-encoding-field | A | 55 | feature | BLOCKED ON A DECISION, do not implement gap 2 as written. The header reserves a text-encoding enum at meta bits 16-23 (PXX_ENC_BYTES/UTF8/UCS2/UCS4, builtinheap.pas:289) that nothing stamps or reads — but the UTF-16 fact is ALREADY spelled a second way, by PXX_KIND_WIDESTR = 5 in the BlockKind byte, shipped and stamped at three sites in builtinwide.pas and read by nothing. Stamping PXX_ENC_UCS2 for wide strings would add a second spelling of a live one, which is the normalise-dont-special-case failure this ticket's own body warns against. Fork filed as decide-the-utf16-payload-fact-is-spelled-twice-kind-widestr-and-enc-ucs2. The ASCII-heuristic interaction this ticket asked to CHECK is measured and UNREACHABLE — see below. | decide-the-utf16-payload-fact-is-spelled-twice-kind-widestr-and-enc-ucs2 |
| feature-a-the-fixedpoint-stamp-could-rebuild-itself-but-every-shape-costs-make-n | A | 30 | feature | The stale-stamp bug now STOPS loudly (527837d3a) instead of printing a false success; making it REBUILD by itself is the remaining half and both implementations were measured out. Recursing with $(MAKE) from the verify recipe breaks make -n outright (GNU make executes a $(MAKE) line under -n, so the dry run deleted the stamp and exited 2). A .PHONY-backed witness file works in a real run but makes make -n compiler/pascal26 always report the loop as planned, because -n must assume a PHONY prerequisite updates its target. Needs a shape that does neither, or a decision that the loud stop is enough. |
— |
| feature-a-the-pascal-reduced-build-must-be-able-to-seed-the-full-compiler | A | 25 | feature | Ruled 2026-08-31: a Pascal-reduced compiler must be able to compile the FULL compiler — it must be a valid seed, not merely self-hosting. Nothing tests this: there is no PXX_NO_* wiring in Makefile or tools/gate.sh at all, so no reduced configuration is built or gated by anything today. Build the pascal-reduced configuration, assert it produces a working umbrella compiler, and wire it where it will actually run. | — |
| feature-a-the-threadvar-area-is-3072-bytes-of-bss-in-every-program-that-has-no-threadvar | A | 45→70 | feature | ROUTES C AND A SHIPPED, AND THE NILPY ARM WITH THEM. A Pascal program naming neither threadvar nor uses, and EVERY NilPy program, now get a ZERO-byte threadvar area automatically -- hello.pas goes bss 38,396 -> 35,324 and a NilPy hello 62,724 -> 59,652, both a flat -3,072, with __pxxTlsBlockSize 1152 instead of 4224. On top of that -dPXX_TLS_USER_0/_1K/_2K/_4K/_8K/_16K sets it explicitly and always wins. THE PASCAL SCAN READS THE SOURCE TEXT, NOT TOKENS, and that is forced: the token array is EMPTY at the only moment the size may be chosen, and the call cannot move down because EmitTlsMainInstall bakes the size into the BSS reservation and the fold captures it. Source is include-expanded by then, which is why the text scan is Pascal-only. THE NILPY ARM NEEDS NO SCAN -- the language has no thread-local spelling -- and the blocker this ticket recorded for it turned out not to exist: a threadvar in a Pascal unit a NilPy program imports reaches the PASCAL allocator, which ERRORS at 0 bytes, naming the unit, the line and the flag. The worst case is a LOUD acceptance regression carrying its own remedy, never a silent collision, and today over a population of zero. C IS EXCLUDED FOR A MEASURED REASON: it has __thread, its #includes expand after the size is chosen, and its arm of the allocator WARNS rather than errors -- the declaration becomes one copy shared by every thread and the program still runs. Guards: test_tlsnone26 (Pascal) and test_tlsnonenp26 (NilPy, which imports a real Pascal unit on purpose to put one on the ambient chain). The uses rule stays on the Pascal side because dropping it would REFUSE a program that compiles today, capping that arm's reach at unit-free programs, 11 of 49 under examples/. STILL OPEN: the long-tail frontends (one or Is<X>Frontend term each, unrequested), and route B, still the only unsound route. Found on the way and fixed separately: bug-a-a-threadvar-in-a-units-implementation-section-silently-reads-zero, which was really the -O2 inliner retaining a threadvar read as a plain global. |
— |
| feature-a-tls-stack-bounds-for-cloned-threads | A | 20 | feature | Threads created by __pxxclone leave TLS_SLOT_STACK_LO/_HI zero, so they miss the I/O lock's TLS fast path and still pay a gettid syscall per I/O statement. The stub knows the top of the child's stack and not the bottom; only the allocator does. Two ways to close it, both cheap, neither obviously right. | — |
| feature-a-typeinfo-integer-name-under-strict-fpc | A | 20 | feature | TypeInfo of a plain Integer rename reports Integer; FPC reports LongInt. decide-typeinfo-scalar-name-spelling settled this on 2026-08-21 -- keep ours by default, report FPC's under --strict-fpc -- and cited this slug as its Implementation. It was never filed. Measured NOT delivered: the name is Integer under default, --mimic-fpc and --strict-fpc alike. |
— |
| feature-a-unreferenced-class-rtti-keeps-every-method-alive | A | 30→70 | feature | LARGEST REMAINING RUNG OF THE ESP UMBRELLA AS OF 2026-09-20 (frankS) -- AND THE PASS SPECIFIED BELOW DOES NOT REACH IT. Measured with --dce-why on the nilpy-c3 demo once the eval() interpreter stopped being linked: 172,637 B / 149 bodies are rooted vmt/rtti slot, 21% of the live image, and the classes holding those slots are all LEGITIMATELY LIVE (TPyList, TPyDict, TPyFile), so an unreferenced-CLASS criterion reaches none of it. The cost is a per-METHOD slot nothing can dispatch to: TPyFile.writelines alone heads three of the nine largest rows, 115,606 B, because it accepts any sequence and drags the iterator-drain path into a program that never opens a file. That is consistent with the NEVER FIRES FOR NILPY finding below rather than contradicting it -- the registry work cleared a different root. Whoever takes this should start from the 2026-09-20 section, which names the chains and says what the instrument cannot yet answer (a per-root SUBTREE total). || THE BLOCKER IS REMOVED AND THE PASS IS NOW WRITABLE AND UNWRITTEN, 2026-09-19 (frankB, 5bde993c5). The registry is no longer an unconditional root: it is emitted only when the program contains a reader -- a parsed __rttireg() node -- so a program that never reflects no longer roots every class by name. That was the one thing standing between this ticket and its own proposal, and it cost 24 B directly on a hello (8-byte count slot + one 16-byte entry), which is NOT the win and must not travel as one: no blob is dropped, because Pass 1 still reserves a header per class for ClassName and the is/as backlink chain. What remains is the pass itself -- nobody has written it, and the next seat should read the PARKED/BUILT sections in the body before starting. IT NEVER FIRES FOR NILPY, by construction and not by degree: every NilPy program pulls pylib, pylib.pas:40 is uses ... typinfo, and typinfo.pas:755 is reg := __rttireg(), so print(1) alone reports reader=1. That weakens the ESP/SRAM case on NilPy specifically -- measure both frontends or name the one you measured. A follow-on that would fix it (move GetClass to its own unit) is fully analysed, priced at ~24-40 B against a four-file uses rewrite in Track B's ground, and DECLINED as a second enabler for a pass that does not exist; its prerequisite check is recorded UNRUN and labelled. || WHY IT WAS THE BLOCKER, MEASURED 2026-09-18 and still the reason the pass is shaped this way: ClassIsStreamable is ClassHasPublished or ClassImplementsGuidedInterface, and a class declared with NO visibility keyword defaults to PUBLISHED -- so TU = class ... end reports streamable=1 and TU = class public ... end reports 0, identical blob/vmt either side, the registry entry being the 24-byte difference in data=. Registry membership is name-reachability at run time, which this ticket's own Watch out lists as what makes a blob undroppable, so the pass AS SPECIFIED would have dropped almost nothing on ordinary Pascal while that held. And the ticket's own headline example is one of them: TInterfacedObject, which holds every method in the residue table, is streamable=1 because it implements a guided interface. THE LEVER WAS THAT THE REGISTRY WAS AN UNCONDITIONAL ROOT: it is emitted whenever any class is streamable and its only consumer is IR_RTTI_REG from one AST node (AN_RTTI_REG, verified across all six backends), so in a program that never asks for it the registry is dead data rooting every streamable class. Making it conditional (done, 5bde993c5) stops streamable being a root, at which point the residue becomes droppable -- that is the state the tree is in now. MEASURED: AN_RTTI_REG comes only from the __rttireg() intrinsic, which inside lib/ is called from three files (rtl/typinfo.pas GetClass, pcl/controls.pas, pcl/gtk3widgets.pas); a program with no uses contains ZERO GetClass/FindClass/typinfo symbols in its object, so typinfo is not pulled ambiently and its registry is emitted and never read -- silently, because emit.inc DROPS an unresolved registry reference rather than failing. It keys on the NODE and never on uses typinfo, since __rttireg() is a public intrinsic a user program can call directly; the flag is set at PARSE time because EmitRTTI runs before any IR lowering, which makes it conservative -- a __rttireg() in never-lowered code still emits the registry. THE --emit-obj EDGE IS MOOT, measured not argued: two objects in one binary, B finds its OWN class and NOT A's (a=1 B_finds_A=0 B_finds_its_own=1), because each object carries its own Data[] and its own registry -- so the cross-object lookup cannot be broken by node-conditional emission and no --emit-obj arm is needed. SEPARATE DEFECT UNCOVERED: emit.inc DROPS an unresolved registry reference and the intrinsic reads nil, so a registry with no reader gives no diagnostic and a reader with no registry gives no refusal -- the silent-negative shape, and why this went unnoticed. Weights via the new PXXDBG=a.rttiweight, profile named: hello hosted x86-64 = 5 classes, RTTI data 664, direct VMT-slot code 409; hello esp32c3 BARE --dce = 1 class, data 160 (the interface machinery is not pulled in on bare at all); esp_pal_fdsem_baseline.pas as an IDF xtensa object = 7 classes, data 608, direct code 556. directmethbytes is neither bound cleanly -- it over-counts inherited slots and under-counts far more, since the ~3.2 KB here is dominated by PXXTIOGetInterface/PXXIntfIMTOf/PXXVarStrAppend/PXXVarClear, runtime routines the methods REACH rather than methods in any VMT; only dce.inc's walk can price the closure. || AND THE SRAM ACCOUNTING, corrected the same day: MEASURED 2026-09-18 (frankB), AND CORRECTED THE SAME DAY -- ON THE BARE PROFILE CODE IS SRAM. defs.inc's own map: qemu's esp32c3 models internal SRAM as ONE RWX region and the whole image (code+data+bss) loads at the IRAM org, so SRAM(bare) = code + data + bss. A first pass of the measurement below read data/bss as the SRAM and code as the flash -- that is the IDF shape and it is false on bare; caught by frankh-3f. Consequences: (a) --dce saves 54344 B of SRAM on bare (esp32c3 131276 -> 76932, -41%; esp32s3 -36%), not zero -- it is the largest SRAM lever measured on this profile after the 64 KiB heap arena; on IDF, where .text can be flash-mapped, the same removal is a flash win and that leg is unmeasured. (b) One unreferenced class with four virtual methods costs +268 B code and +808 B data = +1076 B, all of it SRAM on bare; the earlier SRAM is 3x the flash cost line is WITHDRAWN as an IDF-shaped split applied to bare numbers. What survives: this ticket's ~3.2 KB headline is the CODE residue (method bodies held by VMT slots), while the blob's own .data bytes -- noted here from the day it opened and never quantified -- are 3x that per class, so the blob is the larger half on either profile and the only half that is SRAM at all on IDF. (c) The fleet's stated bare baseline data=616 bss=70936, SRAM=71552 omits code and is really ~129452 at plain -O. Scales at ~128 B one-off + ~200 B per class + ~120 B per virtual method, and the 4x4 row lands 360 B UNDER that because the fixture shares method NAMES between classes -- a real program pays more, not less. On IDF the blob is .data too with .rela.data +420 and NO .rodata section at all. Ownership settled with frankh-3f: placement is his, reachability and emission are mine, and read-only placement buys ZERO SRAM on bare because moving bytes inside one RWX region changes nothing. |
— |
| feature-a-why-threadsafe-needs-45pct-more-global-fixups | A | 20 | feature | --threadsafe self-compile emits 45% more global fixups than the normal one (65657 vs 45326). Raising the cap unblocked it; nobody has explained the +45%, and it may be one fixup per TLS access that dedupes away | — |
| feature-bare-esp-supports-uses-builtin | A+S | 20 | feature | Make uses builtin; compile on a bare ESP boot |
— |
| feature-cli-widgetset-flag | A | 20 | feature | CLI: --widgetset=<name> as sugar for -dWIDGETSET_<NAME>, so the flag reads like Lazarus' -ws | — |
| feature-cross-frontend-interop-contract | A | 20 | feature | Cross-frontend interop contract — umbrella | — |
| feature-dynamic-include-paths-config | A | 25 | feature | Get host paths out of the compiler and into config. FOUR slices landed: -I/-Fu search roots (2026-06-20), pxx.cfg tier 3 (2026-08-21), the /usr/include fallback as a discovered TABLE (2026-08-26), and per-directory library manifests -- pxxlib.cfg supplying define/undef/mode to units under one tree and nothing else (2026-08-31), which was the load-bearing one and is what makes PasApplyMimicDefines's NEVER-during-a-self-build landmine structural. DEMOTED 55 -> 25 on 2026-08-31 and RE-MEASURED 2026-09-01: none of the three remaining bullets has a consumer, and two are near-zero value as specified -- the soname fallback table is UNREACHABLE on a normal Linux host (all nine stems resolve from ld.so.cache, which is asked first), and an xtensa build needs no generated config. The only open question is intent, filed as [[decide-is-a-host-sdk-scanner-still-wanted-now-that-nothing-needs-one]]. Do not take this for its title - the big half is landed. | decide-is-a-host-sdk-scanner-still-wanted-now-that-nothing-needs-one |
| feature-n-a-quoted-from-import-reaches-another-language | A | 15 | feature | import 'sysutils.pas' as su works; from 'sysutils.pas' import Trim does not. The quoted cross-language import was built for the PLAIN arm only, because the from-arms thread impName/impRoot through member binding, alias recording and PyStdAliasRecord. Nothing needs it today — the refusal diagnostic points at the plain spelling, which works — so this is filed to be visible rather than to be urgent. |
— |
| feature-nilpy-arc-cross-parity | A | 25 | feature | NilPy object-ARC cross-target parity (aarch64 inline arms + scope-exit) | — |
| feature-nilpy-cycle-collector | A | 35 | feature | NilPy: collect reference cycles (the reserved half of the GC decision) | feature-nilpy-object-reclamation |
| feature-opt-a-wide-string-literal-should-be-a-static-block-not-a-runtime-transcode | A+O | 30 | feature | w := 'lit' on a WideString calls PXXWideFromStr at runtime and allocates, where the narrow s := 'lit' is a bare pointer store into a static block InternStr already laid down complete with [meta][rc][len]. The wide literal could be the same — transcoded at compile time, zero allocation — but InternStr unconditionally stamps MSTR_FLAG_ASCII|MSTR_FLAG_ASCII_KNOWN, which builtinwide.pas DELIBERATELY refuses to stamp on a wide block, so a folded literal would carry a flag its runtime twin rejects, marked KNOWN so nothing rescans. Needs a wide-aware intern entry point and an MSTR_KIND_WIDESTR constant in defs.inc. |
— |
| feature-opt-alloc-intent-hint | A+O | 10 | feature | Allocation-intent hint: tell the RTL growth policy how a buffer will be used | — |
| feature-opt-arch-level-and-dispatch | A+O | 25 | feature | What x86-64 feature level does pxx emit for? Referenced as 'if raised' by two existing tickets and never filed; raised by the user 2026-08-15 when FMA came up. MEASURED: our own gate box plexus is a Xeon E5-2620 v2 (Ivy Bridge, 2013) with AVX but NO FMA and no AVX2 — x86-64-v2, not v3. So a v2 bump is safe and FMA would SIGILL on the machine that gates every push. Includes the answer to the 'dispatch defeats inlining' objection: multiversion whole FUNCTIONS, not instructions. | — |
| feature-opt-inline-bodies-with-a-statement-level-call | A+O | 35 | feature | 67 distinct functions across 13 example programs plus compiler.pas are rejected by the inline statement validator solely because their body contains a bare procedure-call statement -- the second-largest blocker measured, ahead of for (32) and case (19), and behind only while (102). No ticket names this shape. The non-leaf machinery already handles calls in EXPRESSION position (InlineBodyHasCall forces argument temp-capture); this is the same calls in STATEMENT position, which the AN_SEQ walker rejects outright. |
— |
| feature-opt-inline-procedures-the-third-admission-axis | A+O | 35 | feature | The inliner has NEVER inlined a procedure, at any -O level, however trivial the body: TryRetainInlineBody opens with if not Procs[procIdx].IsFunc then Exit. Measured with identical bodies -- function AddF(a,b) inlines at -O3 while procedure SetG(v); begin g := v; end is not even retained. This is a THIRD admission axis (is-function) alongside return type and body shape, and no other ticket names it. Bounded by measurement at 1.43x in the slot-op shape (six calls per iteration, 0.200s -> 0.140s, ~3.3 ns per call) -- so it is real, general, and NOT the fix for the NilPy subscript gap it was found under, where the classification compares are worth 2.80x more than the call removal. |
— |
| feature-opt-inline-record-splice-into-the-caller-destination | A+O | 35 | feature | Record-returning leaves inline at -O3 (78fc2dab3) and deliver 1.54x on the dd kernels against a 3.0-3.3x hand-inlined bound -- so 40-50% of the available win is still on the table, and it is ONE mechanism: the splice allocates a Result temp with the callee's layout and copies out of it, where hand-inlining writes the caller's destination directly. Measured, not inferred: same arithmetic, bit-identical output, only the temp differs. | — |
| feature-opt-o3-register-pressure | A+O | 20 | feature | -O3 register-pressure tier: operand scheduler + liveness-scaffold register allocator | — |
| feature-opt-rtti-emit-on-use | A+O | 32→70 | feature | RTTI is emitted unconditionally (every class, even a classless program) — dead weight on ESP32/embedded | — |
| feature-port-multi-os-abstraction | A | 55 | feature | UMBRELLA: abstract the target-OS axis — FreeBSD (native) + Windows (PE, Wine-tested), phased | feature-port-freebsd-native, feature-port-rtl-over-libc, feature-port-windows-pe |
| feature-port-openbsd-libc | A | 25 | feature | OpenBSD/amd64 target — route RTL through libc.so. The LOWERING landed (feature-port-rtl-over-libc, 3a0ed43fb); what remains is the target. Re-blocked 2026-08-31 on decide-openbsd-pinsyscalls-vs-the-rt-sigreturn-residual, because this ticket's own item 3 says its acceptance criterion ('no raw syscall') is WRONG and must be settled first: the measured residual is 1 instruction non-threaded and 4 threaded, each irreducible. It had been sitting at the head of Track A's ready queue on a satisfied blocker, offering an unstartable job to every idle agent — it also needs an OpenBSD VM built by qemu autoinstall, which is infrastructure nobody has stood up. | decide-openbsd-pinsyscalls-vs-the-rt-sigreturn-residual |
| feature-rtl-libc-frontend-sites-and-thread-errno | A | 40 | feature | Finish --rtl-libc: convert the C/Rust/Zig frontend syscall sites, and test the thread errno hazard the raw clone stub creates | — |
| feature-t-run-the-wasi-slices-under-wasmtime-as-a-strict-second-host | A | 25 | feature | Every check in test/wasm/ runs its module under node's WASI, which does not enforce the pointer alignment WASI preview1 requires of u64 out-params. Measured: with that defect reinstated, the align slice prints every expected line under node and exits 0, and traps under wasmtime before its first line. check_align.sh now covers the specific calls, but the four general WASI slices — sysio, loadfile, pal, wasi — still run under the lenient host only, so the class stays invisible wherever they are the coverage. | — |
| feature-typeinfo-last-categories | A | 20 | feature | The tail of the TypeInfo widening that still has NO consumer: interfaces (14) and metaclasses (28) are refused outright, TypeInfo(PChar) is refused while bare Pointer works, Currency (4) needs a tyCurrency that does not exist, procvar/method types get no TTypeData, and NativeInt reports tkInteger where FPC reports tkInt64 on a 64-bit target. | — |
| feature-writeln-as-library | A | 40 | feature | PHASE 1 COMPLETE 2026-09-01, both slices: variadic bracket-elision -- Log('x=', x) against procedure Log(const a: array of const) -- now works for BARE ROUTINE calls (slice 1) and for METHOD calls (slice 2: instance, class, virtual, chained selector, and with fixed parameters ahead of the vector, in statement and expression position). Slice 2 also FIXED A PRE-EXISTING SILENT CRASH it uncovered: g.D('one'), a single elided element, compiled cleanly and segfaulted on the pinned compiler because the method loops passed a scalar where a vector was required with no diagnostic. PHASE 3 COMPLETE 2026-09-05 (65b62b148): lib/rtl/libwriteln.pas renders every type byte-identically to the builtin, asserted as PAIRS so a divergence names the type; all formatting is in Pascal and only 'emit bytes to a descriptor' stays in the compiler. Phase 3 was only worth doing once phase 1 landed -- this ticket's own 2026-07-20 note calling it 'a strictly worse writeln nobody would call' was true then and stale from 2026-09-01. THREE known holes, all in the BOXING and so unreachable from a vector reader: a QWord >= 2^63 renders signed (filed), the sized booleans render 1/0 (filed, and appended as a fourth consumer to decide-how-a-type-carries-an-identity-its-kind-cannot-hold), and a Single renders as a Double -- that last is NOT a defect, fpc has no vtSingle either, and its parity row is asserted as DIFFERENT so it goes red if one ever appears. PHASE 2 BRACKET SPELLING COMPLETE 2026-09-05: Log(['x=', x:8:2]) parses and renders byte-identically to the builtin for float w:p, integer, Int64, QWord, Boolean, string, Char, variable width and narrow width. AND IT NEEDED NO vtFormatted TAG, which is a deliberate departure from this ticket's design -- TextStrArg already formats exactly these types for the write statement's variable-width path, through the same builtin formatters Str(...) lowers to, so a formatted element renders to an ordinary string node and boxes as a managed string. Every existing TVarRec consumer therefore reads it unchanged (sysutils.Format's FmtArg*, libwriteln, and vendored FPC code doing case VType of, which would have met an unknown tag 19 and fallen through its else). A new tag would have been a second copy of a formatter we already have. NOTE THE JUSTIFICATION IN THE DESIGN BELOW IS WRONG: it says :w:p here is 'where FPC parses width/precision too', conflating the write STATEMENT with an array-of-const literal -- measured, fpc 3.2.2 refuses Log(['x=', x:8:2]) with Syntax error, "]" expected but ":" found. This is an EXTENSION, and what it earns is the gap between the builtin writeln(x:8:2) and a library one. PHASE 2 ELIDED SPELLING STILL NOT DONE and still blocked for the reason recorded below: it needs the shared argument loops loosened, and frankA's refactor-a-carve-the-nilpy-arms-out-of-the-shared-pascal-argument-loops is status: working with 25 isNilPy references left in pasparser_expr.inc, five of them in those very loops (1227, 1272, 1302, 1382, 1385) -- checked with frankA 2026-09-05 rather than assumed from the four commits whose subjects name that slug. Do NOT replace the builtin writeln: compiler.pas self-hosts on it. |
refactor-a-carve-the-nilpy-arms-out-of-the-shared-pascal-argument-loops |
| idea-a-auto-enable-threadsafe-by-restarting-the-compile | A | 5 | idea | Auto-enable --threadsafe by voiding the compile and restarting |
— |
| idea-a-fold-the-asm-emit-harness-mock-preludes-into-one | A | 35 | idea | Fold the five asm-emit harnesses' mock preludes into one shared include | — |
| idea-adaptive-heap-growth | A | 5 | idea | Adaptive heap growth policy (research / north-star — not scheduled) | — |
| idea-cross-namespace-ambiguity-warning | A | 15 | idea | Warn when a call name matches in BOTH the Pascal and C namespaces | — |
| meta-a-pxx-produces-linkable-code | A | 80 | meta | Standing umbrella, priced ABOVE bug fixing by the owner 2026-08-31. OBJECT OUTPUT WORKS ON FOUR TARGETS: x86-64 (41045d7b4, frankC), i386 (writeELFRel386General, frankC), riscv32 and xtensa -- a gcc-built main links a pxx object and calls into it; clang and tcc link the same object; export surface is the C-convention routines. x86-64 OUTPUT IS NOW POSITION-INDEPENDENT and needs no link flags at all (d0537380a / 44b256356 / a3b1af61a, frankA): .text carries no absolute relocation, so the object links into a PIE and a non-PIE, under gcc and clang, and survives a hardened -Wl,-z,text. i386 still needs -no-pie. --SHARED NOW WORKS FOR COMPILED SOURCES (0419bab94, frankA): a Pascal or C source builds a .so that both links with ld and loads with dlopen (NilPy cannot: only the Pascal and C frontends can mark a routine cdecl, so nothing is exportable), with virtual dispatch, heap, managed strings and libc calls working inside it. i386 ALREADY PAID THE DEBT IT WAS PRICED FOR: the C-ABI trigger fired and says OPTION A, and it exposed bug-a-i386-clobbers-ebx-across-a-cdecl-exported-function (fixed 7a30658e7 -- ebx, esi AND edi). WHAT REMAINS: arm32 + aarch64 object output (p45, feature-a-object-output-for-arm32-and-aarch64, explicitly NOT urgent -- they are a second and third oracle for a ruling already made, and aarch64 is gated on making cparser.inc's positional param spill AAPCS first), i386 position independence is DONE, in two commits and by two agents: frankC converted the load, store, address-as-immediate-into-a-register, ProcAddrFix and DynCall shapes (518 R_386_32 in a Pascal object to 0), and frankA converted the last family, an address used as an IMMEDIATE with no destination register to borrow -- push imm32 and mov [reg],imm32 -- which only appears once a program uses an RTL unit (62 to 0 for uses sysutils, cd4af7824). An i386 object now links under gcc -m32 -pie -Wl,-z,text with no DT_TEXTREL, for both frontends, RTL-using or not. The first census was scoped to fixtures that use no RTL unit, which is why it read as complete twice, and a Pascal library unit (worth LESS than it looked -- cdecl on a definition is already a working export spelling). AND OBJECTS/LIBRARIES NOW RUN THEIR INITIALISERS (41b08f2bf / c1bb99ec2 / 0148feacf / 3dd98fe32, frankA): before, every pre-main value a foreign consumer read was unset -- a .so and an --emit-obj object both compiled their initialisers in and nothing called them. x86-64 and i386 objects now carry .init_array/.fini_array and a .so carries DT_INIT/DT_FINI, both frontends. AND SEPARATE COMPILATION NOW WORKS AT ALL (243137302, frankA): two --emit-obj objects used to collide on 116 crtl symbols and could only be linked with -z muldefs; the runtime is now exported WEAK and, the part that actually mattered, an exported definition relocates against its own SYMBOL rather than this object's .bss section -- without which the link succeeded and each object still read its own errno (0 where gcc says 2). Two objects share one heap, one errno and one optind, matching a gcc build of the same sources. Still N copies of crtl BY SIZE (580088 bytes against 310544 for one object), which is a separate blocker above. pxx can now be linked into most things on x86-64; the remaining gap is targets and runtime duplication, not output kinds. |
bug-a-a-pascal-global-cannot-import-a-c-global, bug-a-errno-is-one-global-across-all-threads-so-a-thread-reads-another-threads-failure, bug-a-the-esp-object-writer-exports-only-app-main-so-no-cdecl-routine-or-global-is-linkable, feature-a-every-emit-obj-object-links-its-own-full-copy-of-crtl-so-n-objects-cost-n-runtimes, feature-a-pxx-cannot-link-its-own-objects-so-a-freestanding-multi-object-program-needs-gcc |
| meta-constant-normalisation | A | 20 | meta | Standing index: stop writing compiler code that branches on constant-vs-variable. Each constant expression becomes its own uniquely-named read-only variable, so downstream has ONE shape to handle. Goal is less double work on future fixes, not speed. | — |
| perf-o-the-variant-hidden-dest-clear-is-a-proc-call-where-the-store-arm-uses-an-inline-blob | A | 35→95 | perf | THE TITLE IS WRONG IN THE DIRECTION THAT INFLATES THE PRIZE AND THE FIX IS SMALLER THAN THE BODY CLAIMS (frankh-c0, 2026-09-22, re-read at HEAD). The store arm does NOT use an inline blob -- the blob is OUT OF LINE and reached by a call, and it exists because the inline spelling cost ~42% of output on a zero-byte .npy and ~99% of the text assembler's traffic. BOTH PATHS CALL. The real asymmetry is that IRBuildHiddenDest calls the PORTABLE Pascal proc (arg node + frame) while IR_VAR_STORE calls the TARGET'S OWN blob (no arg node, no frame, preserves rax) -- and a census of every backend shows ONLY x86-64 has a divergent fast spelling: four backends already call the portable proc from both paths and are uniform, aarch64 has its own helper. So the body's "every backend needs the arm" is true of the new-IR-kind approach and NOT of the asymmetry named here, which is x86-64-local. The saving is argument marshalling plus a frame, not a whole call. COST EVIDENCE IS SYNTHETIC AND SEVEN DAYS OLD (+14%/+8% against a binary that no longer exists) and the ticket's own retirement condition asks for a real program: lekkerzeilen-7a has been asked for dispatch as a share of a ROOFS frame. THE CARRIER IS PER CALL SITE, NOT PER CALL -- re-derived at HEAD 2026-09-22 with PXXDBG=a.ir: two k.m(t) sites mint two DISTINCT unnamed carriers (557, 558), each cleared by a call before its hidden-dest virtual_call, so a loop over one site reuses ONE slot. The dynamic cost is per CALL and a release sweep's population is per SITE; multiplying a per-slot win by call frequency mixes them. NOT measured: whether the carrier still owns a reference after the var_store -- that is perf-a's question. THE TIMING HALF IS DEFERRED AND THE PRODUCER IS NOW PAUSED WITH NO DATE -- the owner stopped GUI testing of lekkerzeilen on 2026-09-22 ("stop gui testing lekkerzeilen for a while please"), so the roofs row this ticket defers to is blocked on him lifting a pause, NOT on a seat or a measurement; do not read this as work in flight. PARKED AND UNOWNED for that reason -- the blocked-by edge to task-e survives and will resurface this ticket when the row lands, which is the entire reason the edge replaced a promise. The ORIGINAL deferral reason stands and is unrelated: load was 14.35, and 7a measured the same binary/scene/pin at 530ms quiet vs 624ms while peers merely COMPILED, with CPython moving 66% against pxx 18%, so contention is DIFFERENTIAL and a ratio is unbounded until both arms run interleaved on a quiet box. | task-e-decompose-a-lekkerzeilen-roofs-frame-so-two-perf-tickets-stop-guessing-at-their-own-prize |
| refactor-a-backend-machine-code-lives-in-six-shared-files | A | 25 | refactor | A backend is not ir_codegen_<arch>.inc + asmtext_<arch>.inc. Six shared files emit or name per-arch machine code: symtab.inc (three full function epilogues), asmenc.inc (inline-asm text for all five targets), ir_codegen.inc (the shared -O pipeline calls two aarch64 passes by name), asmfront.inc, exception_emit.inc, and -- the one that crosses a lane -- cparser.inc, the C FRONTEND, which writes the C start entry stub as raw rv32/a64_/arm32_ emission. Measured by the omission defines, which turn every one of these into a compile error. | — |
| refactor-a-c-exclusive-lowering-has-no-carved-out-file-so-track-c-cannot-be-staffed | A | 60 | refactor | C owns its lexer/parser/preproc but NOT its lowering: ir.inc carries 40 CProgramMode references. So most Track C work needs Track A's files, and a C agent cannot be staffed independently -- measured 2026-08-29, four of six ranked C tickets need an A file. | — |
| refactor-a-nilpy-calling-convention-logic-lives-in-the-pascal-parser-files | A | 25 | refactor | 78 isNilPy branches sit inside the pasparser_*.inc set — NilPy language rules living in files named for the Pascal parser. It is why a Track N ticket routes its holder into files Track N does not own, and it is the-substrate-is-ast-and-ir-not-the-parser violated by filename rather than by design. |
— |
| refactor-a-nilpy-const-str-bypasses-both-the-literal-fast-path-and-the-call-arg-funnel | A | 45 | refactor | A Nil-Python string literal lowers to an IR const_str tagged tyString, | — |
| refactor-a-nodearrndinfo-is-a-symtab-query-living-in-a-pascal-parser-file | A | 30 | refactor | NodeArrNDInfo contains no Pascal syntax and no Pascal semantics — it is a pure symbol-table query (SymArrNDims / UFldArrNDims / SymArrDimSpan) that happens to live in pasparser_call.inc. Track C now calls it across the frontend boundary, which the-substrate-is-ast-and-ir-not-the-parser.md warns against. The doctrine violation is the FILE, not the call: move it to symtab.inc. | — |
| refactor-a-one-rule-spelled-two-ways-at-two-strictnesses-in-ir-lowering | A | 40 | refactor | ir.inc:10426 reads (CProgramMode or IsNodePChar(dest)) -- one rule expressed two ways at two different strictnesses, with the dialect flag standing in for the property it implies. Normalising it DELETES an entry from the C carve-out inventory rather than moving one, so it makes that refactor smaller. |
— |
| refactor-a-search-path-helpers-live-in-the-c-preprocessor | A | 18 | refactor | AddPasUnitDir / AddPasIncDir / AddCIncludeDir are generic search-path functions that live in cpreproc.inc, so compiler.pas's own -Fu/-I handling depends on the C frontend. Six of the eleven errors from omitting the C frontend are this misplacement, not coupling: moving them drops omit-c from 11 sites to about 4. | — |
| refactor-a-seven-frontends-borrow-rust-parser-helpers | A | 22 | refactor | Zig, ALGOL, Erlang, Fortran, LOLCODE and Whitespace all call five helpers whose bodies live in rparser.inc, so PXX_NO_RUST alone fails with 198 errors and Rust can only be omitted together with all six. Three different layers are marooned under one R prefix: AST constructors (share, wrong file), RWiden (numeric widening — SEMANTICS, should not be shared at all), and REmitParamRegSpill (raw x86-64 emission in a frontend). | — |
| refactor-a-seven-places-answer-which-time-syscall-on-which-target | A | 45 | refactor | A CENSUS, not a proposal: seven sites in this tree independently answer 'which clock/timer syscall number, and how wide is its timespec, on this target'. THREE WERE WRONG AND ONE OF THOSE HUNG — all three had their own local number table. The one site that routes through the PAL instead (pxxcio) was fixed for free by fixing the PAL and needed no edit. The rule they each rediscover is one sentence: riscv32 is time64-only, so it needs the *_time64 number AND a 64-bit timespec on a 32-bit machine, while i386/arm32/xtensa are the opposite. Three of the seven genuinely CANNOT route through the PAL (palfutex, palpthread and builtin.pas are below or beside it by design), so the fix is not 'make everything call the PAL' — it is to stop the rule being folklore. Not urgent: nothing is known-broken today after this evening's fixes. Filed so the eighth site does not rediscover it by hanging. | — |
| refactor-a-the-assignment-kind-funnel-needs-a-third-discriminator-not-a-third-special-case | A | 55 | refactor | AssignKindsIncompatible(dstTk, srcTk) (symtab.inc:4003) sees only two TYPE KINDS, and if dstTk = srcTk then Exit(False) is its second line -- so every pair the kind channel cannot express arrives as a MATCHING pair and is actively CERTIFIED rather than merely missed. THREE sibling checks now sit beside its one call site in ir.inc rescuing exactly that: enum identity (:12346, two enum types are both tyEnum -- used to store TFruit's 1 into a TColor and read back green), fixed-array-to-dynamic (:12420, both sides are the ELEMENT's kind -- stored the static array's ADDRESS in the handle slot, Length(d)=4310328 then SIGSEGV where fpc prints three elements, refused at af9c92a6f), and the procedural-value/bare-routine-name check above them. Each rescues one pair on its own channel: SemId, array depth plus Kind <> skParam, node shape. TWO IS A SMELL AND THREE IS A DESIGN FLAW (CLAUDE.md, root-cause-over-microfix). THE COUNT IS SETTLED AT THREE -- frankS read the enum sibling after this was filed and corrected their own count from two. THE FIX SHAPE IS THEIRS AND IT IS NOT A FOURTH DISCRIMINATOR: AssignKindsIncompatible returns a Boolean where the honest answer has a THIRD value, the kind cannot decide this. What the three checks have in common is that each already knows the kind's answer is not authoritative for its shape, so the thing to try is a predicate asking WHETHER THE KIND IS AUTHORITATIVE HERE, consulted BEFORE the comparison, with the three existing checks as its ARMS rather than its siblings. A discriminator bolted alongside is a fourth special case in disguise. THE FOURTH INSTANCE HAS ARRIVED (frankD, 12af8ef60) AND IT NAMES A DIFFERENT AXIS THAN PREDICTED, WHICH IS THE WHOLE VALUE OF A FOURTH. The predicate frankS proposed ALREADY EXISTS -- AssignSideKind (ir.inc:183), consulted before the comparison, whose False return IS the third value, with five bails in its ident arm each commented the kind is not authoritative for this side. The real defect is that it is implemented TWICE, PER NODE SHAPE, and the copies had drifted: the field arm carried two bails where the ident arm had five, missing IsArray first, so array of <record> was refused as a FIELD and accepted as a VARIABLE -- same question, two answers, decided by spelling. AND THE AXIS SPLITS THE SIBLINGS INTO TWO GROUPS THAT ARE NOT ONE GROUP: per-side AUTHORITY (the bails, collapsible into one node-shape-independent question) versus pairwise IDENTITY (enum identity and fixed-to-dynamic, facts about both sides together that NO per-side predicate can express). So the honest shape is TWO changes, not one, and collapsing all five would put a pairwise fact in a per-side slot. Unscheduled by both seats; frankD offers a handover of the per-side half. |
— |
| refactor-a-the-for-in-exception-runtime-trigger-is-the-whole-token-shape | A | 30 | refactor | Every program with a for .. in now pulls the exception runtime -- measured +4096 bytes of stubs on x86-64, xtensa and riscv32 alike, including a program whose only for-in is over an array and which cannot raise. Correct but over-broad: only the class-enumerator and generator forms synthesise a try/finally, and a TOKEN pre-scan cannot tell them from for c in s without the symbol table. Narrowing needs a post-parse emission point for the stubs, not a better guess in the scan. |
— |
| refactor-a-the-frozen-string-store-body-is-written-twice-in-three-backends | A | 30 | refactor | i386, aarch64 and arm32 each hold the frozen string[N] store body TWICE — once in IR_STORE_SYM (s := v) and once in IR_STORE_MEM (p^ := v) — identical instruction for instruction, differing only in where the capacity comes from (SymStrCap[si] against Integer(IRIVal[node])). MEASURED, not asserted: after e4cba526a the tyChar arm now appears twice in each of the three files, six copies where there were three, so the duplication GREW as a side effect of fixing the bug it caused. That bug is exactly what duplication predicts: only the SYM copy had ever grown a tyChar arm, so s := c compiled and p^ := c did not, on precisely the three backends with no second copy of it (bug-a-char-into-shortstring-through-a-pointer-is-x86-64-only). THE EXTRACTION IS ALREADY WRITTEN AND WAS MEASURED: one EmitFrozenStrStoreBody<arch> per backend called by both arms, byte-identical across 32 corpus binaries on each of i386/aarch64/arm32 with ZERO changed. Net line change was measured at -12 / -8 / +7 (i386 / aarch64 / arm32) on the PRE-transplant tree, where that diff was extraction AND the char fix together; the deletion against today's tree is larger because a second copy now exists per file, and that number is NOT measured. It is NOT landed — the transplant won the race and the tree is in a stand-down window for the prio-100 shortstring relayout. THE TRANSPLANT'S OWN AUTHOR ARGUES FOR THIS: frankC, 2026-09-02, "the author of the transplant thinks the extraction supersedes it... I would rather the tree ended up right than that my version stayed in it", having named normalise-dont-special-case in the transplant commit itself. The banked patch is REFERENCE ONLY and does NOT apply — it was cut against the pre-transplant tree; re-derive rather than git apply. |
feature-p-implement-the-real-tyshortstring-byte-prefix-layout |
| refactor-a-unify-the-five-remaining-pascal-postfix-suffix-walks | A | 35 | refactor | Successor to the six-copies ticket. Its inventory measured all six copies against FPC and found only site 6 diverging; site 6 is fixed. Unifying the remaining five is worth doing to prevent the NEXT site 6, but it has zero measured behavioural payoff and five conversions of regression risk, so it is filed at its real priority rather than inherited. Blocking design question inside: ASTIVal on an AN_DEREF currently means two different things. | — |
| refactor-a-viscachevis-is-indexed-by-a-string-id-and-sized-by-a-unit-count | A | 45 | refactor | VisCacheVis is subscripted by a Strs[] index but sized by MAX_UNITS, a unit COUNT. The two are unrelated quantities, so the array's bound has no relationship to the values that index it. Three range checks now stand between that mismatch and memory corruption; one of them was missing and cost a multi-session bug (bug-a-a-deep-unit-dependency-parses-with-a-spliced-token-stream). Separate the domains so the checks are belt-and-braces rather than load-bearing. | — |
| refactor-a-wasm32-is-the-one-target-the-shared-scope-exit-sweep-cannot-be-ported-to-as-a-port | A | 25 | refactor | The scope-exit managed-local sweep is now emitted ONCE per body and CALLed, on all six targets that write into a flat Code[]: x86-64 50e25f5f0, i386 3d7cde305, arm32 4a1a80184, aarch64 5f89103c9, riscv32 b1554c59a, xtensa (Call0) dde109a7a. wasm32 is the seventh target and is deliberately NOT in that sequence, so this ticket exists to stop it reading as unfinished work. wasm32 has structured control flow and no raw code addresses: WasmEmitManagedLocals writes into the epilogue, the backend writes no Code[] at all, and EmitProcEpilog is never called for it -- EmitManagedLocalCleanupForTarget's first statement is if TargetArch = TARGET_WASM32 then Exit. There is nothing to place at an address and nothing to CALL. A shared sweep there would have to be a REAL wasm function taking the frame as an argument, and the locals are wasm locals rather than frame slots at an offset from a base pointer, so there is nothing to pass -- a different change with a different correctness argument, not the same one with different encodings. MEASURED, not read: the byte-identity A/B for every one of the six landings shows --target=wasm32 identical=30 differs=0, i.e. wasm32's codegen has not moved by one byte through the whole sequence, which is the intended outcome. VALUE IS UNKNOWN AND SHOULD BE MEASURED BEFORE ANY WORK: the win on the flat-code targets is code size (compiler.pas 30.4% smaller on riscv32, 46.2% on xtensa under --xtensa-long-calls), and a wasm module's size behaves differently enough that the payoff is not inherited from those numbers. Prio 25 because nothing is broken -- the inline sweep on wasm32 is correct today. |
— |
| task-a-a-fix-on-one-backend-should-name-what-it-checked-on-the-others | A | 40 | task | Three fixed-on-one-target-left-on-the-others defects surfaced in one night, all by the same mechanism: a fix is written where the bug was observed, and the sibling backends have no observer. normalise-dont-special-case.md already says to grep for the sibling before closing; it is not being followed, and one of the three shows why -- the unfixed sibling's own comment ADMITTED the gap and nothing routed a reader to it. | — |
| task-a-add-fu-to-the-compiler-usage-line | A | 40 | task | One line: -FuDIR is missing from the compiler's own usage: output, so the flag that makes a third-party Python package resolvable is undiscoverable from the compiler itself. The docs half is done (doc-n-fu-is-how-a-python-package-is-found); this is the code half that ticket split off. |
— |
| task-a-devdocs-developer-is-83-unowned-pages-and-73-are-two-months-stale | A | 40 | task | devdocs/developer/ is 83 .md files that CLAUDE.md and devdocs/dev/README.md both fail to name, so no lane owns it. 73 of 83 were last touched on 2026-06-26 by the commit that CREATED the tree, and that same commit broke citations inside it: 35 of 157 distinct cited paths do not resolve, including one that points at docs/historic/ for a file the split moved to devdocs/developer/historic/. Rationale is measured, not assumed: across the whole night's audit, doc accuracy tracked WHO IS ACCOUNTABLE for a page, not how many people read it -- docs/** (owned by D, fewer readers who could check it) was more accurate than devdocs/dev/** (heavily read, unowned). | — |
backlog-nilpy (184)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| addendum-2026-09-16-value-parity-and-the-first-fully-restatable-toolchain | A | 50 | addendum | Addendum, 2026-09-16 — value parity on 7bf3860e0, and a toolchain that can be restated | — |
| addendum-the-property-warning-fires-on-the-safe-pair-and-is-silent-on-the-lethal-one | A | 50 | addendum | Addendum: the property warning fires on the SAFE pair and is silent on the lethal one | — |
| bug-n-a-bare-import-of-a-c-header-only-name-builds-a-binary-that-cannot-exec | N | 50 | bug | import strings in a .npy resolves to /usr/include/strings.h, synthesises libstrings.so from the header's own file NAME, and emits a DT_NEEDED no loader can satisfy. Verified against the PINNED compiler (2026-09-10): the build succeeds, readelf -d shows Shared library: [libstrings.so], and the program dies at exec with cannot open shared object file. At HEAD it is a compile error instead, because e53eff428's guard catches exactly this -- so the OBSERVABLE has already moved from silent-broken-binary to loud-refusal, and this row is about the remaining half: nothing should have emitted that DT_NEEDED in the first place. NOT a resolution bug: strings is DELIBERATELY absent from pasparser_proc.inc's curated bare-import list (an ordinary Pascal unit sharing a Python name, named there beside classes and types), so falling through to the host header is the documented behaviour. Six of the seven RTL/header name collisions on this box -- math, menu, netdb, png, regex, zlib -- resolve to the unit; strings is the one that reaches a header, which is why nobody hit this before. |
feature-n-derive-a-header-s-library-from-its-directory-and-verify-it-against-the-library-s-own-dynsym |
| bug-n-a-bare-nilpy-import-falls-through-to-a-host-c-header-of-the-same-name-and-says-nothing | N | 35 | bug | — | |
| bug-n-a-bare-tuple-returned-from-a-mimic-module-method-arrives-as-its-string-repr | N | 60 | bug | A BARE tuple returned from a method of a class in a mimic (lib/rtl) module arrives at the caller as a STRING holding the tuple's repr: type(r) is str and r == (a, b) is False. A tuple inside a returned LIST survives intact, and the identical code in a plain local module or inline is correct -- so this is the mimic-module return route, not tuples, not dict iteration, and not imports generally. Found as the single failing assertion (24 of 25) in test/lib_mimic_xml_sax_xmlreader.npy, which Track T auto-filed against an unrelated compiler-only sha. | — |
| bug-n-a-bitwise-or-shift-operator-on-a-variant-user-object-never-reaches-its-dunder | N | 45→70 | bug | c & 12 where c reads as a VARIANT holding a user object never reaches and: it coerces the object to an int instead. Same for |, ^, << and >>. This is what blocks the augmented halves &=, |=, ^=, <<= and >>=. |
— |
| bug-n-a-builtin-function-is-not-a-first-class-value | N | 45 | bug | call_it(print, x) gives error: undefined variable (print) while call_it(own_fn, x) works -- a USER function is a value and a BUILTIN is not. Ordinary Python: a builtin passed as a callback. Three sites in lekkerzeilen's entry-point closure, all announce=None if quiet else print, which is what walls the demo at app.py:561. The conditional is not involved; a bare argument reproduces it. |
— |
| bug-n-a-call-result-discarded-in-a-boolean-context-is-never-released | N | 45 | bug | > | — |
| bug-n-a-chained-assignment-through-a-call-result-target-still-stores-right-to-left | N | 25 | bug | box(1)[idx(\"i\")] = box(2)[idx(\"j\")] = 7 evaluates its target subexpressions in the wrong order and prints the right values, so nothing can see it. Measured 2026-09-10 against the FIXED chain arm: pxx gives [1, 2, 'j', 'i'] where CPython gives [1, 'i', 2, 'j'], and both stores land. This is the NAMED RESIDUAL of bug-n-a-chained-assignment-to-two-attributes-does-not-parse, which put every target shape whose receiver is a NAME onto one left-to-right arm; a target whose receiver is a CALL is not one of those shapes, so it still falls through to PyParseLValueAST's nested right-associative reading. Ranked low deliberately, not because the divergence is small but because a chain whose targets are call results is not a shape any corpus here writes -- one measured instance, written by hand to find the boundary. What makes it worth a row at all is that it is SILENT: the values are right and only the side effects of the target subexpressions differ, which is the same property that let the general case sit unreported. |
— |
| bug-n-a-char-key-and-a-string-key-are-equal-everywhere-except-in-a-dict | N | 40 | bug | pylib treats VT_CHAR and VT_STRING as ONE string type in ordering, repr, concat and text extraction — but PyVarEq bails on p^.VType <> q^.VType before it ever gets there, and PyVarHashKey has no VT_CHAR arm either. So a char-tagged key stores fine and then misses every lookup. No NilPy-reachable repro today (the pystr_ofchar boundary converts at every crossing), but this is the mechanism that turned Counter(str) into a SILENT 0 instead of a loud KeyError. |
— |
| bug-n-a-class-level-field-annotation-is-discarded-unless-the-class-is-a-dataclass | N | 75 | bug | x: float in a plain class body is parsed, accepted, and its TYPE thrown away, so every read of the field is a variant; the identical two lines under @dataclass type it correctly. Measured 25.8x on arithmetic over such fields. |
— |
| bug-n-a-class-level-method-read-off-a-class-value-as-a-value-is-refused | N | 45 | bug | > | — |
| bug-n-a-class-level-method-through-a-class-value-is-refused-when-the-name-has-two-carriers | N | 40 | bug | > | — |
| bug-n-a-classmethod-cannot-call-another-through-cls | N | 55 | bug | A classmethod cannot reach another one through its own receiver | — |
| bug-n-a-collections-deque-segfaults-at-run-time | N | 70 | bug | collections.deque() COMPILES and then SEGFAULTS at run time (rc=139), producing no output at all where CPython prints a value. Minimal: q = collections.deque(); q.append(5); print(q.pop()) inside a function -- compiles clean, crashes. MEASURED ON BOTH SIDES of the 2026-09-12 candidate-promotion fix, with binaries built from the same tree minus that one hunk, so it is PRE-EXISTING and unrelated to it. The pin cannot serve as a control because it predates deque support entirely (no member deque came of the qualifier collections). A compiling program that crashes is worse than a refused one, and the crash is silent -- no diagnostic, no partial output. |
— |
| bug-n-a-def-in-an-imported-module-does-not-shadow-len-or-sorted | N | 55 | bug | > | — |
| bug-n-a-def-inside-a-taken-branch-does-not-rebind-the-name | N | 65 | bug | RE-RANKED 45 -> 65 2026-09-20 ON A SECOND OBSERVABLE THAT REFUTES THIS TICKET'S OWN by-design ESCAPE: a conditional def with NO PRIOR DEFINITION is not a rebinding question -- there is nothing to displace -- and it is REFUSED outright, error: unresolved forward: <name>. That makes the standard pure-Python fallback try: from x import f / except ImportError: def f(...) fail, which is HALF OF PYTHON'S ONLY #ifdef: pxx supports the conditional IMPORT (the owner ruled that idiom by design, 2026-09-20) and not the conditional DEFINITION. It is NOT if-specific -- if/for/try/finally all refuse -- and NOT a visibility problem, because a call from INSIDE the same block fails identically. Original observable: def g(): return 1 followed by if True: def g(): return 2 still calls the FIRST g. Split out of bug-n-a-module-level-rebinding-still-loses-to-a-def-of-the-same-name when that one was fixed: it is a different mechanism — the def side, not the assignment side. A nested def has a position, but PyRegisterDefShells only walks module-level defs at DEPTH 0, so a def inside a branch never gets one. |
— |
| bug-n-a-dynamic-attribute-store-on-a-scalar-variant-segfaults | N | 70 | bug | xs[0].foo = 1 SEGFAULTS (rc 139) where CPython raises 'int' object has no attribute 'foo' and no __dict__ for setting new attributes. pydynattr_set_v checks the CLASS-REFERENCE tag and nothing else, so a scalar-tagged variant falls through to pydynattr_set(pyvarobj(v), ...) and scalar bits are reinterpreted as an object address. THE MECHANISM, so this does not decay when the instance is fixed: ONE concept with TWO runtime entry points whose authors held different beliefs about the same population -- the twin pydynattr_get_v DOES check the tag and its own comment says why (for any other tag (str/int/float/bool) it is scalar bits reinterpreted as an address, and ClassName on that would dereference garbage), while set_v's comment says Only a CLASS REFERENCE needs telling apart here, which is the claim that is false. Found by grepping for the sibling while fixing the GETTER's compile-time twin (bug-n-an-attribute-on-a-scalar-returned-by-a-call-segfaults), not by a test. Measured 2026-09-21 at HEAD; the getter's fix does not touch this and the two are independent. compiler/builtin/pylib.pas. |
— |
| bug-n-a-for-in-loop-that-rebinds-its-own-name-leaves-the-thread-registry-undrained | N | 40 | bug | for t in ts: where t already holds one of the elements leaves the thread registry undrained |
— |
| bug-n-a-free-function-keyword-argument-is-refused-in-a-pyeval-interpreted-lambda-body | N | 45 | bug | lambda x: g(x, outside=2.0) <= 5.0 dies at RUN time with pyeval: unsupported keyword arg: outside, where g is a free function. The discriminator is NOT free-vs-method and NOT the keyword: it is whether the body gets LIFTED. A body that is a bare call (lambda x: g(x, outside=2.0)) is compiled and correct; wrapping the same call in a comparison routes the body through pyeval, whose keyword handling is hard-wired to print's end/sep/flush and errors on anything else (compiler/builtin/pyeval.pas:4040). The METHOD spelling of the same shape works through the comparison, which is why this reads as a free-vs-method bug and is not one. Measured 2026-09-12 while clearing the float-literal-in-a-lambda wall; app.py:3305 is the METHOD form and is CORRECT (verified against CPython), so this does NOT block the lekkerzeilen closure. Honest run-time refusal, not a wrong value. A real fix needs the callee's signature at run time so a keyword can be mapped to a parameter slot, which pyeval does not have — that is the actual work, and it is why this is not a microfix. |
— |
| bug-n-a-freshly-allocated-value-whose-result-is-discarded-is-never-released | N | 70 | bug | A call whose FRESH result is discarded never releases it -- methods and containers | — |
| bug-n-a-from-import-alias-resolves-its-source-through-flat-scope | N | 60 | bug | from M import X as Y resolves the SOURCE name X through flat unit scope instead of through M, so any equal name in flat scope wins. TWO SEVERITIES, ONE CAUSE: a collision INSIDE one import statement is now a compile error (undefined variable), but two DIFFERENT modules each exporting the same member name is still a SILENT WRONG VALUE -- both aliases answer the later module (measured 2026-09-11, 8b0839edde8f). Prio 45 -> 60 on the silent arm, which the 2026-09-10 re-measure concluded had gone and had not varied the module axis. The collision needs no alias: from M import X beside ANY other imported module that defines X reads the wrong one. PyImportedGlobalSym (source unit per from-import entry) resolves it correctly and the field pre-pass already uses it; the value door still does not. |
— |
| bug-n-a-frozenset-returned-from-a-def-arrives-empty | N | 60 | bug | A frozenset returned from a def arrives at the caller EMPTY -- len 0, repr 'frozenset()', membership False -- no matter how it was built. set, list, dict and tuple returned from the same shape are all correct, and a frozenset that never crosses a return is correct too. Silent data loss, no crash, no error. | — |
| bug-n-a-function-value-has-no-name | N | 30 | bug | A def as a VALUE carries no name. f.__name__ on a def name refuses at COMPILE time (undefined variable (f)), and on a function held in a variable or parameter raises AttributeError at run time, because the boxed callable has only a code address. repr() shows it too: <function at 0x5ae82b>, where CPython prints <function f at 0x...>. A user class and, since 2026-09-19, a builtin type both answer name, so a def is the one callable that cannot. |
— |
| bug-n-a-keyword-argument-does-not-bind-when-a-constructor-overload-set-contains-a-zero-parameter-arm | N | 45 | bug | queue.Queue(maxsize=2) -- app.py:575's exact spelling -- fails with Queue() is missing a value for parameter 1, which has no default, while queue.Queue(2) works and the parameter really is named maxsize. Measured 2026-09-10 at compiler 61f8a78f8aae with three controls that narrow it: a keyword binds fine on a NilPy-defined class's init, on a Pascal shim METHOD, and on a Pascal constructor overload set whose arms all take at least one argument (array.array(tc=\"h\") resolves against Create(tc)/Create(tc,init) and works). The one shape that fails is an overload set containing a ZERO-PARAMETER arm, where the keyword has to select the other one. NOT the same defect as bug-n-an-overloaded-constructor-is-picked-by-name-ignoring-argument-type, which is about which arm is chosen for POSITIONAL arguments; this is about a keyword failing to bind after an arm is chosen. Worked around in mimic_queue by collapsing to one constructor with a default (Create(maxsize: Integer = 0)), marked REVERT TO TWO OVERLOADS. |
— |
| bug-n-a-keyword-argument-through-a-callable-field-is-refused | N | 25 | bug | A keyword argument on a run-time-dispatched call REFUSES when the name resolves to a CALLABLE FIELD rather than a method: TypeError: zap() is dispatched at run time through a callable attribute, which takes positional arguments only. CPython runs the same program. Method dispatch carries keywords correctly (pydyn_methkw binds by name against the RTTI's parameter names); the callable-field arm goes through pyvar_callv*, which takes POSITIONS and has no names, so the refusal is a deliberate choice of an error over a silent mis-binding. A NilPy def and lambda DO carry a signature at run time (feature-n-a-callable-value-carries-its-signature-type, pybound_new_sig), so the names may already be reachable from the callable's own value -- that is the thing to measure before designing anything. |
— |
| bug-n-a-keyword-argument-through-a-callable-value-is-refused-above-four-positionals | N | 55 | bug | pascal26:531: error: Nil Python: a keyword argument through a callable value needs pyvar_callv_kw (pyeval) and at most 4 positional arguments — TSP row 8, and it REPRODUCES at pin v414 (aeadb1754b80b622) AND at HEAD, so it is not fixed by events. SITE: tsp/departure.py:530-531, dv, _, miss = aim(model, t_end, nbody.to_bary(tuple(fl.state), fl.t_us), t_arrive, target, tol=1000.0) — five positional and one keyword. CONFIRMED BY ELIMINATION, not by the line number: six modules in tsp/ have a line 531 and only this one matches the diagnostic's shape (>4 positional AND a keyword); ascent.py:531 is an arithmetic assignment and __main__.py:531 has a keyword but ONE positional. AND THE OBVIOUS TEST IS INVALID — COMPILING departure.py DIRECTLY IS CLEAN, rc=0, ON BOTH COMPILERS. The row fires only when the module is reached through tsp/historic.py:26's from . import anchor, ascent, departure, .... So pxx tsp/departure.py answers NO to a defect that is there, and a seat checking the fix that way will believe it landed. Reach it through historic.py. MECHANISM CANDIDATE, UNTESTED: aim enters by a FUNCTION-LOCAL relative import, from .anchor import aim at departure.py:519; through historic.py the anchor module is already bound at module level, and the diagnostic turns on the callee being a VALUE rather than a known function, so the entry point plausibly decides which of the two aim is. Recorded as a candidate because nothing here tested it. NEAR-DUP CHECKED, NOT MERGED, AND THE DISTINCTION IS THE ARITY: bug-n-a-keyword-argument-through-a-callable-field-is-refused is the RUNTIME refusal of a keyword through a callable FIELD (o.zap(1, mode=2), ONE positional), where the field-dispatch arm falls to pyvar_callv0..4 which take positions and have no names. THIS ticket is a COMPILE-TIME refusal where pyvar_callv_kw is exactly what the diagnostic asks for and the blocker is the >4 POSITIONAL cap. Same family -- the pyvar_callv0..4 arity ceiling is visible in both -- and plausibly one root, which is why they are linked here rather than merged: the observables, the phase and the repro all differ, and closing one must not be read as closing the other. If a fix raises the callv arity ceiling, check both. PROVENANCE: filed by frankz-e5's survey as historic.py:531, which was the no-filename trap — an error inside an imported module prints that module's line with NO file name, so the reader supplies the file they invoked; corrected to departure.py by e5 and frankuser on construct+arity+import-edge agreement, and the compile that was asked for as confirmation instead produced the entry-point asymmetry above. |
— |
| bug-n-a-keyword-argument-through-a-class-value-is-refused-at-runtime | N | 55 | bug | cls(x, b=99) — a keyword argument to a class reached as a VALUE — raises TypeError at run time saying such a callable 'still carries no parameter names'. It does: RTTI_METH_FLAG's paramKinds block has carried param NAMES since the reflection work, and PyClassRefNew does not read them. The static spelling P(x, b=99) is correct, so this is one construction path disagreeing with the other. |
— |
| bug-n-a-keyword-argument-through-a-procedural-field-needs-a-plain-receiver | N | 55 | bug | H().fn(1, b=2) and hs[0].fn(1, b=2) are error: undefined variable (b) where h.fn(1, b=2) and g().fn(1, b=2) answer correctly — a KEYWORD argument to a callable FIELD, only when the receiver is a constructor call or a subscript. The keyword name parses as an expression, the same symptom the statically-unknown-callee ticket had. |
— |
| bug-n-a-keyword-beside-a-mapping-at-an-ordinary-method-call-is-refused | N | 40 | bug | MECHANISM: at a method with no *args/**kwargs (the arity-driven call paths, five sites that divert a starred argument into PyStarExpandCallArgs), a name=value argument is bound through PyKwArgIndex BEFORE the **mapping is seen, and the mapping then fills a contiguous run of slots from the positional count on. So a keyword and a mapping in one call collide: o.m(1, b=2, **d) is refused as 'positional argument after keyword argument', and o.m(**d, a=1, b=2) as 'got multiple values for parameter a'. Loud refusals, never a wrong value. The constructor and the **kwargs-collecting method paths already fold the whole keyword tail into one mapping (PyParseKwTailIntoMapping + PyStarFillFromMapping, switched at the first keyword when PyDStarAheadInArgs); the fix is the same switch at these sites. |
— |
| bug-n-a-lambda-returning-a-captured-heap-value-yields-none | N | 60 | bug | A lambda whose body is a captured heap-typed value returns None: lv = [1]; (lambda: lv)() is None, not [1]. Holds for list, dict, tuple and bytes; str and int are fine, a literal body is fine, a parameter passthrough is fine, and a nested def with the identical body is fine. Silent wrong VALUE in ordinary Python, and it makes lambda-based test probes lie. |
— |
| bug-n-a-lambda-returning-a-user-class-instance-yields-none | N | 62 | bug | > | — |
| bug-n-a-lambda-stored-in-a-class-attribute-is-not-callable | N | 45 | bug | class gl: clear = lambda a: a * 3 then gl.clear(2) raises TypeError: object is not callable at run time; CPython prints 6. The same lambda bound to a MODULE-level name works (f = lambda a: a * 3; f(2) gives 6 in both), so it is the class-attribute store that loses the callable, not the lambda. Measured 2026-09-10 at compiler 98b6545b4652. PRE-EXISTING and verified as such: it reproduces with and without the staticmethod(...) wrapper that was being added the same afternoon, so it is not that arm's doing -- the control without the wrapper fails identically. Compiles clean and fails at RUN time, which is the bad half: a class-as-namespace whose members are lambdas is accepted by the compiler and dies on first call. |
— |
| bug-n-a-list-and-a-set-share-one-class-so-introspection-cannot-tell-them-apart | N | 45 | bug | hasattr([1], 'add') and hasattr([1], 'update') are True: list and set are both TPyList at run time, so every is-test-based introspection answers set questions about a list. type(x).__name__ DOES tell them apart, so the discriminator exists and the predicate is not using it. |
— |
| bug-n-a-local-bound-to-both-a-pascal-class-and-its-subclass-loses-subscripting | N | 30 | bug | A name bound at one site to a Pascal class and at another to a NilPy SUBCLASS of it aborts at run time with TypeError: object is not subscriptable on the subscript, where CPython works. Measured 2026-09-09: g = array.array(\"h\", bytes(2)); print(g[0]); g = Grid(\"h\") with class Grid(array.array) fails at the FIRST subscript -- the one compiled before the subclass binding exists -- so it is the name's resolved TYPE that is wrong, not the operation. Three controls narrow it: the same name bound twice to the SAME Pascal class works; a Grid instance subscripted with no second binding works; and pure NilPy classes with getitem rebound base->subclass work. So it is specific to a PASCAL class's default indexed property plus two bindings whose classes are related by inheritance. Loud, not silent. |
— |
| bug-n-a-local-bound-to-self-loses-its-class-and-an-omitted-default-then-segfaults | N | 35 | bug | > | — |
| bug-n-a-local-holding-a-callable-is-shadowed-by-a-pascal-intrinsic-at-the-call | N | 70 | bug | lo = f then lo(2) prints 2 and hi = f then hi(2) prints 0, where CPython prints f's result. A NilPy LOCAL holding a callable, spelled like a Pascal intrinsic, is answered by the INTRINSIC at the call — no diagnostic, no crash, a plausible wrong number. abs = f is the same. ord = f is CORRECT, which is the control that makes this a shadowing bug rather than a builtin-name policy: ord is a Python builtin too and it binds the local. The assignment is fine — the value is built correctly — so this is the CALL door reading the name, and f(2) on the same def is right throughout. Found while testing the module-member-as-a-value group; it made an unrelated test row red for a reason nothing in that row could explain. |
— |
| bug-n-a-method-receiver-parameter-must-be-literally-named-self-or-every-argument-shifts | N | 70 | bug | A NilPy method's receiver parameter must be literally named self. TWO distinct failures, isolated by varying one method at a time: a non-self receiver in __init__ never creates the attribute (AttributeError: 'K' object has no attribute 'x', rc 217), and a non-self receiver in a plain method SEGFAULTS when the receiver is a local (rc 139) while working when it is an inline construction. Eight names swept per axis; only self passes either. The @classmethod path binds by POSITION and is correct for ANY spelling, including zz_whatever — so the machinery a fix needs is a few lines away. In the pin. CPython requires nothing of the name. |
— |
| bug-n-a-module-level-instance-called-by-name-in-a-function-constructs-instead-of-calling | N | 58 | bug | > | — |
| bug-n-a-nested-class-is-hoisted-to-module-scope-and-is-not-an-attribute-of-its-enclosing-class | N | 45 | bug | > | — |
| bug-n-a-plain-function-as-a-class-attribute-does-not-bind-the-receiver | N | 40 | bug | class C: plain = two then c.plain(7) on an INSTANCE does not pass the instance. CPython's plain-function-becomes-method rule binds it -- def two(a, b) reached as c.plain(7) gets a = the C instance, b = 7 and prints a=C b=7 -- and we raise TypeError: missing 1 required positional argument(s). Loud rather than silent, which is the good direction, and a real divergence on an idiom people write. Found 2026-09-10 while landing bug-n-staticmethod-is-not-a-value: the unwrapped plain = f row was going to be that fix's CONTROL, and CPython's own oracle refused it -- which is the point, since binding the receiver is exactly the rule staticmethod exists in the language to opt OUT of. Read through the CLASS (C.plain(7)) the two agree, so the divergence is the instance door only. NOT the same as bug-n-a-staticmethod-read-through-an-instance-binds-a-receiver (p25), which is about type(k.stat).__name__ on a DECORATED method: that one binds where it should not, this one fails to bind where it should. |
— |
| bug-n-a-procedure-shim-in-value-position-yields-a-number-not-none | N | 45 | bug | A dotted stdlib call whose pylib shim is a PROCEDURE prints a number when used as a value: print(sys.stdout.flush()) printed 1 where CPython prints None. The AN_CALL node is built the same way for a procedure as for a function, so the value read is whatever sits in the result slot — a plausible wrong number rather than a refusal. Found while adding sys.stdout.flush and fixed FOR THAT ENTRY by returning pynone; the mechanism is unchanged and pysys_exit is the other procedure in the table. Either every shim returns a value, or PyParseStdlibCall must refuse a procedure entry in value position. |
— |
| bug-n-a-pylib-temporary-tpylist-is-never-freed-so-format-and-set-leak-per-call | N | 75 | bug | > | — |
| bug-n-a-qualified-def-value-read-is-invisible-when-the-def-s-module-is-parsed-first | N | 60 | bug | > | — |
| bug-n-a-qualified-member-call-still-consults-the-global-c-overload-set | N | 55 | bug | m.open(\"x\") where a C header declares int open(const char *, int) fails with no overload of open matches these arguments -- the qualified member call is checked against the GLOBAL C overload set instead of the module's own member. Sibling of bug-n-a-field-assigned-a-class-or-none-in-two-methods-wont-widen, which fixed the INFERENCE walk's copy of this mistake; this is the CALL path and is still open. Found while trying to build a hermetic fixture for that bug: a hand-written two-parameter declaration of open fails at HEAD, and adding ... to match glibc's variadic form does NOT fix it, so glibc's declaration differs from a hand-written one in some way that was NOT established. |
— |
| bug-n-a-relative-import-lowercases-the-module-name-but-an-absolute-one-does-not | N | 35 | bug | from . import MyMod fails with no unit named mymod -- the name is LOWERCASED before lookup -- while import pkg.MyMod on the same file resolves correctly and prints the right value. Measured 2026-09-11 at c53cb51926a2. Python module names are case-sensitive, so a package with any capital in a module name is reachable through one import door and not the other. Found incidentally: my own test harness named a scratch module m_bigV and the diagnostic said m_bigv. |
— |
| bug-n-a-returned-nested-def-reads-zero-for-its-captures-past-arity-three | N | 60 | bug | > | — |
| bug-n-a-same-named-rtl-unit-shadows-both-a-relative-import-and-a-mimic-shim | N | 30 | bug | RE-RANKED 85 -> 30 ON 2026-09-20 AFTER RE-MEASURING EVERY LIVE ROW AT HEAD: all of arm 2 passes (zlib round trip, member and from-import binding, and a short stream raising CPython's own zlib.error), import random compiles, and platform/textfile/typinfo now give a LOUD refusal naming the remedy rather than binding silently. THE OWNER-SPECIFIED FIX IS BUILT AND THIS TICKET DID NOT SAY SO: his 'importing preference whitelist we can just hardcode' is PyRtlUnitServesPython, 18 entries, and zlib entered it in 80d71d782 -- which is why a section below saying 'zlib is not in that list' is stale. WHAT REMAINS IS TWO UNSPRUNG TRAPS. (1) The mimic-shim precedence trap NOW HAS A GUARD: tools/mimic_shadow_check.py, wired into gate.sh quick, asserting no mimic_X is shadowed by either ours-first substitution; its criterion is set DISJOINTNESS so it carries no baseline count and cannot go stale as lib/ grows. Census at HEAD: 23 shims, 137 unit names, 18 curated, ZERO collisions (the 09-19 row said 34 and 135 -- both carried, different populations, same verdict). (2) The routine-level hazard is UNGUARDED and stays here: a Pascal routine whose name collides with a Python one, whose signature ACCEPTS CPython's argument shape, and whose SEMANTICS differ, returns a wrong value with no diagnostic -- Ldexp is the near miss, two of three, saved by the third. THE 85 OUTLIVED BOTH ARMS: it was set when arm 1 was a wall on the top-ranked target, arm 1 was fixed 2026-09-11, arm 2 landed in 80d71d782, and nobody re-ranked -- the third field in one day where partial closure left a mechanical record stale while an exemplary summary failed to prevent it. The host-C-header fallthrough is NOT this ticket: bug-n-a-bare-nilpy-import-falls-through-to-a-host-c-header-of-the-same-name-and-says-nothing, p35, open. PRIOR SUMMARY FOLLOWS. ARM 2 RE-MEASURED AT HEAD 2026-09-19 AND ITS THREE LIVE ROWS NOW WORK: zlib.decompress(...), zlib.InflateZlib(...) and from zlib import InflateZlib all compile and bind, and a compress/decompress ROUND TRIP matches CPython exactly (b'hello hello hello'); a too-short stream raises CPython's own zlib.error. What REMAINS of arm 2 is only the half that was never a live defect: the mimic-shim PRECEDENCE trap, which is still unsprung -- re-censused at HEAD across 34 mimic names and 135 unit names (the ticket measured 21 and 19) and the two sets are STILL DISJOINT, so no shim is shadowed today and the trap springs only when someone first shims a module we already have a unit for. THE TRAP IS UNSPRUNG AND HERE IS WHAT WOULD SPRING IT -- this clause named math.frexp as a LIVE instance for a few hours on 2026-09-19 and that instance IS NOW FIXED, by a frontend intercept landed the same day (9131cade1); a summary citing a repaired row as the live example of an open trap is the stale-summary failure MINTED BY ITS OWN FIX, which is the nastiest spelling of it. What the episode established, and what this ticket actually holds, is the REASON: Pascal's Frexp and Ldexp both sit AFTER math.pas's own 'Python math surface' block, which ends at Comb. NEITHER WAS WRITTEN AS A PYTHON ENTRY POINT. math.ldexp agreed with CPython only because Pascal's Ldexp(x, e): Double happens to take CPython's argument shape -- LUCK, not design -- and math.frexp refused only because Pascal's is a procedure with two var out-params where CPython returns a PAIR. So the question is not whether a Python spelling reaches a Pascal routine; it is what happens when it does. MEASURED 2026-09-19 over 74 outcome comparisons (the complete 62-name math surface plus 12 shared names across zlib, base64, html, json and re): ZERO silent value divergences, every failure a LOUD compile-time refusal. So the mechanism is safe today for the population that exists. WHAT WOULD SPRING IT is a Pascal routine whose name collides with a Python one, whose signature ACCEPTS CPython's argument shape, and whose SEMANTICS differ -- that one returns a wrong value with no diagnostic, and nothing in the tree would catch it. Ldexp is the near miss: it had two of those three properties and was saved by the third. That is the same shape as the mimic-shim half of this ticket -- real, unsprung, and cheap only for as long as nobody adds the colliding name -- which is why both halves stay HERE rather than being split across the math ticket. Read the rest of this summary as the 2026-09-10 report it was. ARM 1 IS FIXED (2026-09-11, fe40bf55e141 -> cb278748efbf, last section of this file) AND ARM 2 IS WHAT THIS TICKET NOW HOLDS. Read the rest of this summary as the 2026-09-10 report it was; two of its claims about arm 1 are now known wrong and are corrected there. The door was NOT the resolution chain but the ALREADY-COMPILED GUARD in ParseUsesUnit, which returns before any .pas/.py/header probe -- the narrow fix this summary prescribes for arm 1 (a relative import must not consult the global unit namespace) was built and measured as NO CHANGE and was dropped. And the population is FOUR of the 117 lib/rtl unit names -- platform, platform_types, textfile, typinfo -- not nineteen and not the two a later section claimed: 113 were already correct because their Pascal door is closed for a NilPy import anyway, so only a unit the RTL drags in regardless ever reaches that guard, which is why two of the four are not CPython module names at all. Corpus effect: bindings.py advanced off this wall onto platform/init.py:95, modules-compiling delta 0, predicted before the re-run. ARM 2, STILL OPEN AND STILL THE OWNER-SPECIFIED SHAPE: the C-header door, the per-module preference table, and the half-open Pascal door, all exactly as described below. ORIGINAL REPORT, MEASURED 2026-09-10 at 546d4dcbd305. ONE ROOT CAUSE, TWO POPULATIONS: the NilPy unit lookup runs before both relative-module resolution and the mimic_ fallback, so any lib unit sharing a CPython module name wins and then binds NOTHING. 19 lib units collide: ast atexit base64 collections configparser html http io json math pathlib platform random re subprocess tempfile tkinter types zlib. ARM 1, AND IT IS A WALL ON THE TOP-RANKED TARGET: from . import platform is an EXPLICITLY RELATIVE import and lib/rtl/platform.pas (the PAL facade) takes it. THAT IS SUFFICIENT TO PRODUCE pascal26:117: error: no member KEY_ESCAPE came of the qualifier platform in lekkerzeilen/bindings.py AND I HAVE NOT ESTABLISHED IT IS THE CAUSE FIRING THERE: lekkerzeilen's own platform/init.py also fails on its own ([[bug-n-a-module-bound-by-an-import-is-not-a-value]], p90 -- a module is not a first-class value, so return _pxx, "pxx" cannot work), so in THAT tree two independent walls can each produce this message and I measured only that this one can. The shadow is proven independently sufficient by a repro containing neither a failing init nor a backend selection; whoever fixes either arm should re-measure bindings.py rather than assume their fix is the one that clears it. Reproduced in four lines with no lekkerzeilen code (pkg/init.py, pkg/platform.py defining KEY_ESCAPE, pkg/sub.py doing from . import platform), and the POSITIVE CONTROL is the same four lines with the module renamed seam: ok: and it prints 27. So relative imports WORK and the name shadows. ARM 2, AND I GOT THE DOOR WRONG IN THE FIRST VERSION OF THIS TICKET -- CORRECTED SAME SESSION: import zlib does NOT reach lib/rtl/zlib.pas. It reaches the SYSTEM C HEADER /usr/include/zlib.h. Proof by which names bind: zlib.uncompress, zlib.zlibVersion, zlib.deflateInit_ and zlib.crc32 all resolve (C API), while zlib.InflateZlib -- Pascal-only -- answers no member. So there are THREE doors ahead of the shim, not one: a system C header, then a Pascal unit, then a sibling/relative .py, and mimic_ last (pasparser_proc.inc:6500, consulted only after every ordinary lookup has failed'). The C-header door is the worst of the three because it binds a LOT and all of it is wrong for a Python caller: C compresstakes four arguments to CPython's one, which is whyzlib.compress(b"x")answersno overload matchesrather than anything about a missing module. AND THE BINDING IS DEAD ON ARRIVAL: the compiler derives the soname from the HEADER name, givinglibzlib.so, which no machine answers to -- its own diagnostic says a header file name is not a library name. Which collisions take which door is decided by whether a header exists: zlib.h and math.h are present, json.h/io.h/types.h are not, and platform.h is not -- so platform takes the PASCAL door, measured (platform.PAL_STDOUTbinds). So a mimic_zlib COULD NOT BE REACHED IF SOMEONE WROTE ONE, which matters because the owner's standing instruction (2026-09-10) is to craft a shim for a missing lekkerzeilen library feature. TWO DIFFERENT FIXES, do not conflate them. Arm 1 is unambiguous and narrow: a relative import (pyRelLevel > 0) must never consult the global unit namespace at all -- the program said., and no RTL unit can satisfy that. Arm 2 is a precedence call AND THE OWNER HAS RULED ON IT, 2026-09-10: and importing preference whitelist we can just hardcode. 'zlib? -> rtl zlib unless...'-- so the shape is an explicit per-module preference table in the compiler, not an inferred rule, and a table is the right answer precisely because the three doors are not rankable in general (math SHOULD take its shim, platform SHOULD take the relative module, and neither follows from a single global order). The machinery to key it on is already present: PyImportLang records that auseswas written as Python (pyparser.inc:38273, added for exactly this kind of keying). The quoted formimport 'zlib.pas' as zstays as the explicit door to the unit -- it WORKS today and is how this was measured. AND THE PASCAL DOOR IS ITSELF HALF-OPEN: through the quoted import InflateZlib binds, then refuses every argument shape NilPy can build (bytes, bytearray, list-of-int, return-lifted) because it wants hashing.pas'sTByteArray = array of Byte`, so a Python caller cannot reach the unit even when the name resolves. That is why the shim must be PASCAL-side, as mimic_struct.pas already argues for itself. |
— |
| bug-n-a-scalar-expression-class-attribute-declared-after-a-method-reads-none | N | 45 | bug | A class attribute whose initialiser is a PARENTHESISED or compound SCALAR expression (P = (1 + 2), P = 1 + 2) reads None through self from a method declared EARLIER in the class body than the attribute — CPython gives 3. Same compile-order root cause as the container case (bug-n-a-container-class-attribute-...-through-self, fixed 2026-09-13) and a DIFFERENT population: that one reads EMPTY and is fixed by typing the field tyClass in the member pre-pass; this one reads None and is NOT, because the fix there is deliberately restricted to tyClass. Taking PyInferExprType's scalar answer here (tyInt64 for (1 + 2)) SEGFAULTS — the field narrows but the store path still writes the wide value — so the store path is the thing to fix, not the typing. Reading the same attribute through the CLASS is correct, and declaring it ABOVE the reader is correct. |
— |
| bug-n-a-shared-slot-class-attribute-is-invisible-to-the-dynamic-getter | N | 60 | bug | > | — |
| bug-n-a-shim-class-reports-its-pascal-spelling-from-type-name | N | 25 | bug | type(collections.deque()).__name__ answers TPyDeque where CPython answers deque, because the Python-facing name is a type ALIAS and __name__ reports the declared Pascal class. The alias makes construction and annotation work, so the gap is invisible until a program introspects — and introspection is exactly where a program has been told it can trust the answer. A CLASS, not one row: every shim that aliases around a name collision (lib/rtl/pil.pas's Image = TPILImage, and any future one) has it, so the fix belongs wherever __name__ is derived, not in the units. |
— |
| bug-n-a-shim-parameter-typed-as-a-container-blocks-the-callable-value-wrapper | N | 45 | bug | > | — |
| bug-n-a-star-unpack-through-a-callable-value-stops-at-four-arguments | N | 45 | bug | "The LADDER is widened to eight and this ticket is what remains: the | — |
| bug-n-a-staticmethod-called-through-cls-raises-attributeerror | N | 70 | bug | > | — |
| bug-n-a-staticmethod-read-through-an-instance-binds-a-receiver | N | 25 | bug | bug(N): a @staticmethod read through an INSTANCE binds a receiver, so type(k.stat).__name__ says 'method' |
— |
| bug-n-a-store-to-a-getter-only-property-masks-the-getter-from-then-on | N | 45 | bug | A store to a property that has a getter and NO setter falls through to the dynamic-attribute store, and because pydynattr_get consults that store BEFORE the property, every subsequent READ of that name returns the stored value instead of calling the getter. The defect is the MASKING, not the acceptance: a computed property silently becomes a stale data field, and a read that was correct before the write is wrong after it. Springs whenever a program assigns to a read-only property on a receiver whose class the frontend cannot name. | — |
| bug-n-a-subpackage-directory-does-not-resolve-as-a-module | N | 55 | bug | from .inner import X (RELATIVE) where inner is a subpackage directory fails with no unit named inner, while the absolute from pkg.inner import X works — so directory-as-module resolution exists and the relative form just hands the resolver a bare name instead of the package-qualified one. html5lib has three real subpackages (_trie, treebuilders, treewalkers), so this is its next rung. |
bug-a-a-python-module-s-identity-is-its-name-not-its-file |
| bug-n-a-sys-stream-in-a-variable-has-no-methods-and-fails-at-run-time | N | 60 | bug | h = sys.stdout then h.write(x) COMPILES and dies at run time with TypeError: object is not callable, having written nothing. sys.stdout/sys.stderr are modelled as a bare fd INTEGER (AN_INT_LIT, 1 and 2) and an Integer has no methods. The dotted spelling sys.stdout.write(x) was fixed 2026-09-10 by wiring the three-segment table entries sys.stdin already had; this is the spelling the table cannot reach. The fix is to make a stream an OBJECT — pylib's TPyFile already is one — and it cannot land alone: PyParsePrintFile asserts ASTKind = AN_INT_LIT and value 1 or 2, so print's file= handling must move in the same change or every print(..., file=sys.stderr) goes red. |
— |
| bug-n-a-tuple-returning-str-method-prints-raw-memory-when-returned-from-a-def | N | 55 | bug | def p(x: str): return x.partition(' ') prints raw memory instead of ('C', ' ', 'minor'). The same call outside a def is correct, and split/rsplit through the same def-return path are correct. Pre-existing — reproduces on pinned. |
— |
| bug-n-a-tuple-unpacking-assignment-does-not-box-a-callable-value | N | 55 | bug | a, b = lambda x: x + 1, lambda x: x + 2 compiles and then a(1) raises TypeError: object is not callable. The single-target spellings (a = lambda ..., return lambda ...) box the callable so the name is a variant; the tuple-UNPACK targets do not, so each name holds a raw pointer the dynamic-call path does not recognise. |
— |
| bug-n-a-uforth-corpus-timeout-is-reported-as-a-cpython-divergence | N | 55 | bug | Six timeout N literals are hardcoded inside the test-nilpy and test-uforth recipes. The three uforth ones are the damaging pair of shapes: wait $pp || true discards timeout's exit 124, the kill truncates p.out mid-stream, and the truncation is then reported as DIFF <corpus> — a pxx-versus-CPython divergence — and counted into bad. A machine under load thus manufactures a Nil-Python frontend finding. Filed by Track T, which owns the harness but not the Makefile. |
— |
| bug-n-a-unit-alias-bound-in-both-arms-of-a-runtime-try-answers-the-handler-s-module | N | 35 | bug | try: ... except E: from pkg import a as impl / else: from pkg import b as impl -- a unit alias is a COMPILE-TIME first-wins table and the branch is a RUNTIME one, so impl answers the handler's module whichever arm actually runs. Silent wrong value, exit 0. Measured 2026-09-11, identical on 785b25831252 and 8b0839edde8f, so it is untouched by the guarded-import arm work and is NOT the guarded case, which is fixed. No fix proposed: the two representations disagree about when the choice is made. |
— |
| bug-n-a-unit-alias-rebind-is-silently-ignored | N | 40 | bug | from . import a as x then from . import b as x answers A; CPython answers B. FindUnitOrAlias scans the alias table from index 0 and takes the FIRST row for a name, so a rebinding is appended and never reached -- silently, with no diagnostic. Split off from bug-n-a-dead-guarded-import-arm-still-binds-its-unit-alias, which was the same table biting through a dead try arm and is fixed; this is the straight-line half and has NO corpus consumer today. Not merely 'make the scan take the last row': the NilPy shim substitutions (<module> -> mimic_<module>) share this table and are registered globally rather than per statement, so last-wins would change which unit a shimmed module resolves to. The measurement that decides it is whether any shim row is ever legitimately overridden by a later registration. |
— |
| bug-n-a-variant-default-parameter-arrives-as-none-from-nilpy-while-typed-defaults-apply | N | 55 | bug | — | |
| bug-n-a-write-to-a-file-that-is-never-closed-is-silently-lost | N | 70 | bug | open(p, \"w\").write(\"DATA\") creates the file and leaves it EMPTY — no error, no warning, the data is gone. With an explicit .close() or a with block the same write lands correctly, so the buffer exists and nothing drains it when the last reference dies. CPython flushes on deallocation, which is what makes the one-liner a normal idiom rather than a mistake. Not reachable from the lekkerzeilen corpus (it uses with everywhere, 0 sites), which is why this is filed rather than urgent — but it is silent data loss on a shape half of Python writes, and a test that reads back what it wrote is the only thing that can see it. |
— |
| bug-n-abs-of-a-complex-raises-typeerror | N | 12 | bug | abs(z) on a complex raises TypeError: expected a number, got object where CPython returns the magnitude. Found while writing the parity assertion for (-8.0) ** 0.5 — type(), .real, .imag and round() on a complex all match CPython exactly, so abs is the one hole in the set. |
— |
| bug-n-an-aliased-from-import-binds-a-same-named-pascal-routine-the-plain-spelling-refuses | N | 60 | bug | FLOOR DECIDED 2026-09-21 (frankuser): a silent wrong value is ruled out by the project's own rule that real code running wrong is a bug, so both spellings must behave alike and A LOUD REFUSAL SATISFIES IT — this is NOT an open design fork and was mis-filed as one. MECHANISM CORRECTED: it is the BACKING-UNIT arm and PASCAL's case-insensitive routine lookup, not the stdlib alias table, which neither name reaches. from random import Random as R compiles and R() returns the INTEGER 0 where CPython returns an RNG object — it binds Pascal's Random out of the backing unit. The mechanism is a same-named Pascal routine with an accepting signature and different semantics, and the condition that springs it is an ALIAS: the plain spelling from random import Random refuses loudly at the call (no overload of Random matches these arguments) while the aliased spelling is silent. Two spellings of one import, two different answers, and the silent one is the one a program written for CPython uses. Live at HEAD and in pin v414 (binary aeadb1754b80). NOT the case-fold defect fixed in pxx@3d3d90a2d — that one is closed and this one survived it, which is how it was found. |
— |
| bug-n-an-ambiguous-property-store-on-a-dynamic-receiver-has-no-setter-path | N | 30 | bug | > | — |
| bug-n-an-attribute-read-through-a-class-bound-to-a-variable-gives-a-raw-address | N | 80 | bug | SEGFAULTS (rc=139), and the summary said ~5.5e6-with-exit-0 until 2026-09-13 -- a probe written to the old wording checks a VALUE and CANNOT observe a crash. WHERE IT DIES, measured at 7990405c2 with -g -O2 and the .map: __pxxInheritsFrom at mov (%rax),%rax reading the parent at +8, with the class pointer equal to 0x40000000 -- MSTR_STATIC_RC / PXX_STATIC_RC_FLOOR, the never-free REFCOUNT sentinel. A refcount word is being walked as a class pointer, so the fix is an OFF-BY-HEADER-OFFSET and not a wrong slot offset. (gdb frame #1 resolves to PyBoxClassRef and is noise -- no CFI, and that return address follows an exception-frame call.) THE OLD 5.5e6 IS EXPLAINED AND RETIRED: frankz-9c traced it to pyclsattr_bind registering the slot at ~5.6e6, i.e. the bound slot ADDRESS surfacing as the value. TRIGGER IS POSITION, NOT COUNT: anything lexically PRECEDING the accessed class, an import included -- and an import emits nothing, which also rules out "more than one emitting construct"; a statement AFTER the class is harmless, which rules out the last-class hoist-drain family a second way. AND THE OUTCOME IS SENSITIVE TO THE CLASS NAMES (Other, Self, Value, Count, Rtti, Result, Kind give the right answer; Bbb, Index, Data, Node, Entry, Base, Zzz, Bar segfault; other passes and OTHER fails). A semantic property cannot depend on an identifier spelling, so THE DEFECT IS PRESENT IN EVERY ROW AND ONLY THE CRASH IS CONDITIONAL ON LAYOUT -- which is what reconciles this ticket's three recorded observables as one defect. A row that prints the right answer is NOT evidence the bug is absent, and no fixture here may assert a value. Deterministic: three recompiles byte-identical, five runs agree. ROUTE: PyClsAttrRefGet, documented in pylib as the route for a class held as a VALUE, is NEVER CALLED in either the failing or the working case (frankz-9c, instrumented on entry); pyclsattr_inst_get is ruled out too. Identical under pin v408 and at HEAD, so the three class-as-value fixes of 2026-09-13 neither caused nor fixed it. OWNED by frankuser from 2026-09-13. |
— |
| bug-n-an-import-inside-exec-is-silently-skipped-and-execution-continues | N | 25 | bug | exec(\"import math\\nr = math.floor(3.7)\", d, d) — pyeval's tree-walker discards the import statement without a word and keeps going, so the failure surfaces later as pyeval: name not defined: math, naming the module rather than the skipped import. When the imported name is never used there is no error at all and the remaining statements bind normally, which is the accepted-and-ignored failure mode the ambient-exec refusal was explicitly built to avoid. |
— |
| bug-n-an-imported-module-s-star-star-never-installs-pypowhook | N | 30 | bug | ** inside an IMPORTED .py never gets PyPowHook, for TWO independent reasons, and each one alone is sufficient: (1) the pyWantsPow token scan at pyparser.inc:41694 runs before PyParseImportRun at 41769, so an imported module's tokens are not in the array being scanned; (2) an imported .py becomes a UNIT, and a unit's initialisation section runs BEFORE the main body where the assignment is emitted as the first statement — so import-time ** cannot see the hook even when it IS installed. Both measured 2026-09-12 with a control: with ** in main too, import-time printed 1.9952623149688793 and post-start printed CPython's exact 1.9952623149688795. Visible cost today is 1 ulp, NOT a crash — the raise this was found through (0.0 ** fractional -> ValueError: math domain error) was a missing row in pypow_cx and is fixed separately. |
— |
| bug-n-an-int-arm-of-a-conditional-expression-is-rendered-as-a-float | N | 40 | bug | > | — |
| bug-n-an-int-method-on-a-none-receiver-returns-0-instead-of-raising | N | 50 | bug | None.bit_length() returns 0 where CPython raises AttributeError — the int-method arm on a variant receiver unboxes without checking the tag, and None's payload reads as the integer 0. dict/list/str receivers do raise, so None is the one shape that answers. |
— |
| bug-n-an-ordering-dunder-that-returns-a-non-bool-fails-against-a-variant-operand | N | 40 | bug | > | — |
| bug-n-an-overloaded-constructor-is-picked-by-name-ignoring-argument-type | N | 55 | bug | A NilPy construction C(x) on a class with several same-arity constructors runs the FIRST one declared, whatever x is. Measured 2026-09-09: one class with Create(TPyBytes) and Create(TPyList), one unit with which(TPyBytes)/which(TPyList) -- the FUNCTIONS resolve correctly (list->2, bytes->1) and the CONSTRUCTORS both answer 1. Silent: the wrong body runs and whatever it does to the wrong argument type is what the program gets. PyClassCreate picks with FindUMeth(ci,'create'), a by-NAME first match; the type-aware picker FindUMethOverloadAhead exists and is already NilPy-aware, but it works by parsing the arguments speculatively and rewinding, and at PyClassCreate's pick site the arguments are ALREADY parsed -- so the fix is a selector over parsed argument NODES, not a call to the existing one. Blocks writing any shim class whose CPython constructor is type-overloaded; lib/rtl/mimic_array.pas carries a one-ctor + runtime is workaround with a revert-when-fixed note. |
— |
| bug-n-an-override-changing-the-result-type-in-a-late-laid-out-class-is-refused | N | 35 | bug | MECHANISM: when an override's result type differs from its base method's, the override join (PyOverrideRetJoinsToVariant) widens the BASE method's RetType to Variant, which is only sound before any body is compiled. The member prepass (PyClassHeaderSweep) guarantees that for every class it hoists, but a class it DEFERS to PyParseClass -- several bases, a mixin base, an unresolved base, or a subclass of any of those -- is registered after the bodies of its already-hoisted bases have been compiled returning the narrow type. Widening then made a caller read a variant out of an int routine: class D(P, Tag) overriding P.v -> int with a float, then P().v() + 1, is a SIGSEGV on pinned v411. The widening is now refused by name in that position ('... in a class laid out after its base was compiled ...'), so the crash is a diagnostic. COST OF THE REFUSAL: the same shape where only the override is ever called ran correctly on pinned and is now refused too. Workaround for a program: annotate both results with the same type. Fix: run the result join for deferred classes in the prepass as well (the override's signature is known there even when its layout is not), or compile the base bodies after the sweep has joined every override. |
— |
| bug-n-an-unpack-or-chain-store-whose-receiver-is-a-parameter-silently-does-nothing | N | 80 | bug | def f(b): b.s, b.t = 22, 23 COMPILES, RUNS, prints nothing and STORES NOTHING — the fields keep their initial values. Any store through PyUnpackTargetStore (the TUPLE UNPACK and CHAINED-ASSIGNMENT paths, which share it) is silently dropped when the receiver is a PARAMETER. MEASURED 2026-09-12 against the PINNED compiler and HEAD, identical on both, so it is pre-existing and not from the chain widening landed the same day. THE SAME STORE WRITTEN AS A SINGLE STATEMENT IS CORRECT (b.s = 11 works), and a LOCAL or MODULE-LEVEL receiver is correct (a.s, a.t = 31, 32 works) — so the defect is exactly PyUnpackTargetStore + parameter. NO DIAGNOSTIC, and the value it leaves behind is the field's initial value, which is plausible. Found only because a chain fixture happened to use a parameter; the receiver kind a test naturally uses is the one that works, because a test constructs the object where it uses it. |
— |
| bug-n-an-unused-import-edge-makes-a-method-receive-an-instance-of-the-wrong-class | N | 75 | bug | Adding an import of a name that is NEVER REFERENCED makes a method receive an instance of the WRONG CLASS as self — from .world import Grid in wind.py, with Grid unused, kills the demo with "'World' object has no attribute 'flow'" where flow is a field of Environment, so a World is arriving as self inside an Environment method. CPython runs the identical source. Same registration-order family as the import-closure ticket and the OPPOSITE direction — that one is FIXED by adding an edge, this one is CAUSED by adding one, which makes it a hazard for that ticket's own implementation. |
— |
| bug-n-annotating-a-local-that-is-returned-destroys-the-defs-inferred-return-type | N | 80 | bug | t: Holder = Holder(); return t types the CALLER's local as a variant where the unannotated t = Holder() types it correctly — the annotation makes the inference worse, and only at the call site. return self.m() is the same scan's second blind spot. |
— |
| bug-n-async-def-and-await-are-not-implemented | N | 60 | bug | async def is refused -- undefined variable (async), so the keyword is not in the grammar at all. Python 3.5. Distinct from yield-from in that a correct implementation needs an event loop and not just a parser arm, so the honest first step may be deciding how far to go rather than typing. Found by the same probe suite as the sys.version_info ruling. |
— |
| bug-n-augmented-assignment-to-an-unannotated-parameter-silently-loses-the-mutation | N | 70 | bug | An augmented assignment to an unannotated PARAMETER dispatches the plain dunder instead of the in-place one, so the caller never sees the mutation. Seven of twelve operators fixed 2026-09-15; &= |= ^= <<= >>= remain, blocked on the plain bitwise binop not reaching its dunder either. |
bug-n-a-bitwise-or-shift-operator-on-a-variant-user-object-never-reaches-its-dunder |
| bug-n-collections-counter-is-unreachable-through-its-qualified-spelling | N | 35 | bug | collections.Counter() is refused with no member Counter came of the qualifier collections, while the bare Counter() and from collections import Counter both work. Measured 2026-09-09. Cause: collections HAS a backing unit (lib/rtl/collections.pas, a Pascal generic TList unrelated to Python's module), so the qualifier resolves against it and asks it for a member it has never had. deque was fixed on 2026-09-09 by routing collections.deque through the frontend's stdlib-call table, which is consulted BEFORE unit-member lookup; Counter was deliberately NOT routed the same way, because that table re-targets by ARITY and cannot select by argument TYPE, and Counter's two 1-argument overloads differ only by type (TPyList vs AnsiString) -- an entry would compile collections.Counter(s) to whichever arity found first and answer a silently wrong count instead of today's honest refusal. So this is blocked on either type-aware selection in that table, or a different mechanism for qualified stdlib members. |
— |
| bug-n-compiling-html5lib-trie-never-terminates | N | 55 | bug | Compiling library_candidates/html5lib/html5lib/_trie/init.py — five lines — never terminates. Found as a pxx process that had been in state R for 1 day 16:47 on a six-session box, and reproduced bounded: timeout 60 returns 124 after emitting only the shim-resolution notes. No diagnostic, no progress, no exit. |
— |
| bug-n-exec-ignores-a-caller-supplied-builtins-mapping | N | 20 | bug | exec(src, {\"__builtins__\": {}}) — the restricted-exec idiom — raises NameError in CPython and silently resolves builtins anyway in pxx. The caller's explicit instruction to resolve names against THIS mapping is discarded, so working CPython code takes a different path. Upward-compatibility defect, split out of the cosmetic decide-nilpy-exec-injects-a-builtins-key. |
— |
| bug-n-exec-only-publishes-a-def-named-body-and-cannot-call-host-globals | N | 45 | bug | pyeval's exec() publishes exactly one def into the caller's namespace — one literally named body, hand-wired at pyeval.pas:5748 to uforth's wrapper idiom. exec(\"def body(): return 1\", {}, ns); ns[\"body\"]() raises KeyError under pxx and prints 1 under CPython. Two more refusals in the same family: a function in the globals dict is not callable from the exec'd body, and attribute access on a parameter raises 'no RTTI for attribute'. All three are programs CPython accepts and runs, so all three are N bugs by the upward-compatibility rule. |
— |
| bug-n-from-package-import-submodule-binds-nothing-when-the-submodule-is-a-file | N | 40 | bug | MECHANISM (re-measured 2026-09-19 at 2bfcfa8bf23e, and it is an ANCHORING bug, not a spelling one): from .pkg import <submodule-file> resolves only when the package directory is itself a search root. The submodule arm now exists and finds the file -- strace shows <root>/P/platform/_gl.py opened -- and then the unit resolver probes the MANGLED key beside the importer and the DOTTED path under the -Fu ROOTS, so a package one level below a root is never reached. A main script INSIDE the package works; a module of the package driven from outside gives no unit named platform__gl. Fix: derive the package dotted path from the root that contains it. Same anchoring question as bug-n-a-subpackage-directory-does-not-resolve-as-a-module (p55) -- do them together. |
— |
| bug-n-from-package-import-submodule-binds-the-parent-package | N | 40 | bug | from xml.dom import minidom binds minidom to the PARENT package xml.dom, not the submodule. Member lookups then resolve the parent's names silently -- minidom.XHTML_NAMESPACE returns http://www.w3.org/1999/xhtml where CPython raises AttributeError. STILL LIVE at ca814b0aabcc (re-measured 2026-09-10) and still a silent wrong value. BOUNDARY NARROWED: a real filesystem package is CORRECT in all three spellings, measured against a parent and child that both define the same name with different values -- so this is the DOTTED SHIM path (xml.dom flattened to mimic_xml_dom, trailing name dropped) and not from <package> import <submodule> in general. lib/rtl/mimic_xml_dom_minidom.py already exists, so the right target is in the tree, unused. |
— |
| bug-n-getattr-with-a-literal-method-name-on-a-builtin-container-or-str-is-refused | N | 45 | bug | > | — |
| bug-n-hasattr-with-a-computed-name-cannot-see-a-builtin-method | N | 55 | bug | hasattr(x, n) with the name in a VARIABLE answers False for every builtin-container, str, int and float method — n = 'keys'; hasattr(a_dict, n) is False while hasattr(a_dict, 'keys') is True. The literal and computed spellings of one question resolve through two different mechanisms and only the literal one was fixed. |
— |
| bug-n-inline-cast-deref-loses-a-pointer-fields-pointee | N | 55 | bug | compiler/pyparser.inc:44098 carries a byte-identical copy of the alias-cast postfix loop just fixed on the Pascal side: its ^ arm answers the pointee from the ORIGINAL cast's alias every time, so the second ^ in a PRec(x)^.fld^ chain gets the type the CAST points at instead of the type the FIELD points at. The deref happens, only the tag is wrong, so the value is plausible and silently wrong. |
— |
| bug-n-keys-through-an-untyped-receiver-is-not-dispatched-cross-module | N | 55 | bug | other.keys() on an untyped parameter is not dispatched on the receiver when the call sits in an IMPORTED module: it either falls through to the dict-view builtin or binds to a keys() the callee's module declares, and a foreign object reaching a self-iterating keys() segfaults. Reopens bug-n-a-user-classs-keys-items-values-is-dispatched-as-a-dict-view, which was closed on the single-module case. Found by Track B reverting a workaround the closed ticket had unblocked. |
— |
| bug-n-kwargs-collector-alongside-named-params-needs-the-remainder | N | 50 | bug | def f(a=1, **kw) called as f(**{'a':5,'x':7,'y':8}) must give a=5 and kw={'x':7,'y':8} — the collector takes the UNCONSUMED keys. pylib has no helper that subtracts consumed names, and adding one is compiler/builtin/** which NEEDS A PIN, so this is coordinator-scheduled, not worker-startable. |
— |
| bug-n-lekkerzeilen-s-world-path-reads-grids-on-none-after-the-render-loop-starts | N | 75 | bug | > | bug-a-a-pxx-created-thread-shares-glibc-s-thread-pointer-so-two-threads-share-one-malloc-state |
| bug-n-len-does-not-dispatch-len-dunder-on-a-dynamically-typed-value | N | 60 | bug | len(x) raises TypeError: expected an object with a length, got object whenever x's static type was not inferred, even though x's class defines len: an element of a list, a value out of a dict, an unannotated parameter, the return of any self-referencing or recursive function. The same value answers .attr, .method(), for-in and x[i] correctly, so len is the one protocol with no dynamic fallback. | — |
| bug-n-min-and-max-as-a-value-bind-to-the-two-argument-arm-in-the-wrong-unit | N | 55 | bug | > | — |
| bug-n-not-and-invert-read-the-box-of-a-name-assigned-from-arithmetic | N | 70 | bug | a = x + 1 then not a is True and ~a is 4, where CPython says False and -1026. It is not about the operator on the right: +, -, *, >> all trigger it, and so does a later reassignment from a plain literal. int(1025) does NOT. Some spellings return a 62-bit value with a tag in the high nibble (0x3000000000000004), so ~ is complementing a BOX rather than the integer it holds. It changes CONTROL FLOW: if not a: takes the wrong branch, silently. |
— |
| bug-n-object-is-the-one-builtin-type-name-that-is-not-a-value | N | 45 | bug | B = object is undefined variable (object), while t = str, u = int, v = dict all bind and call fine. object is the single builtin type name that is not a first-class value — it is consumed as a no-op in the base-class position and has no row anywhere else, so any expression naming it fails. |
— |
| bug-n-os-has-no-getpid | N | 30 | bug | os.getpid() answers undefined variable (os) — the member is absent, not the module, exactly like os.rmdir was before it was fixed on 2026-09-12. Split out of bug-n-os-has-no-rmdir as its RESIDUAL rather than fixed alongside it, because the two are not the same size: rmdir reuses an existing syscall number (NR_UNLINKAT with AT_REMOVEDIR, since Linux has no at-family rmdir) and cost nothing cross-target, while getpid has no NR_ constant at all and needs a number added to all SIX per-target tables in compiler/builtin/pypal.pas (x86-64/i386/aarch64/arm32/wasm32-as-unsupported/riscv). Low prio because no program in any umbrella calls it — it surfaced in a probe written to characterise the rmdir diagnostic, not in the lekkerzeilen closure. |
— |
| bug-n-property-works-as-a-decorator-but-is-not-a-builtin-name | N | 30 | bug | @property compiles and works, but property as a plain builtin NAME does not exist: v = property(getter) and v = property(getter, setter) both give undefined variable (property). Real CPython code uses the callable form for read/write properties, because @property.setter needs the decorator pair and the two-arg call is the older, shorter spelling. Blocks html5lib's treebuilders/base.py:321 and therefore the whole dom treebuilder. |
— |
| bug-n-pyeval-boxes-a-freshly-built-container-into-a-variant-and-retains-it | N | 60 | bug | pyeval boxes a FRESHLY BUILT container into a variant and then retains it | — |
| bug-n-pyeval-cannot-read-an-exponent-float-literal | N | 35 | bug | pyeval's tokenizer refuses an exponent float literal (1e3, 2E-3) — float exponent literals not supported in M1, compiler/builtin/pyeval.pas:1882. It surfaced when the lambda/closure body reconstruction started carrying float literals at all (2026-09-12): the body is rebuilt as TEXT and re-lexed by pyeval, so lambda: 1e3 would have compiled and died at RUN time. It is refused at COMPILE time instead, by name, so nothing moved later than it already was — that refusal is the current behaviour and this ticket is to replace it with support. Scoped to the INTERPRETED path only: a lifted/compiled lambda body never reaches pyeval and handles exponents fine. A SECOND question is in the same tokenizer and should be answered in the same visit, but is NOT the same bug and must not be silently folded in: the fraction is accumulated digit-by-digit against scale := scale * 0.1 (pyeval.pas:1873-1879), which is not the same arithmetic as StrToDoubleBits, so an interpreted literal and a compiled one can differ in the last place. Rank the exponent gap on the refusal; rank the accumulation on evidence that a real program cares, per the F-lane rule. |
— |
| bug-n-pyfixiterableargs-is-inert-its-own-test-passes-with-it-disabled | N | 45 | bug | MEASURED. PyFixIterableArgs (pyparser.inc:21694) can be disabled at its first line -- Result := False; if True then Exit; -- and test/test_nilpy_user_iterable_in_builtins.npy, the test that exists to cover it, emits a BYTE-IDENTICAL binary and identical 37-line output, still matching CPython. So does the rest of the NilPy corpus tried. Either the mechanism has been superseded by another path and is dead code, or it is entirely uncovered; both are defects and they need different fixes. Found while proving a DIFFERENT set of arms dead -- this one is a live call site whose removal nothing notices, which is the more dangerous shape. |
— |
| bug-n-pyparser-property-accessor-sites-do-not-know-an-interface-receiver | N | 30 | bug | pyparser.inc has ~9 hand-written copies of the property-accessor call decision, and each knows exactly two answers (AN_VIRTUAL_CALL / AN_CALL, Self at argument 0). The choice is three-way: an interface receiver needs AN_INTF_CALL, slot in ASTSOffset, Self from the fat pointer. The Pascal-side twins had the identical defect and were fixed by extracting one MakeAccessorCall (0f0fd6642); pyparser.inc was deliberately NOT touched because it is Track N's file and N is parked. NOT KNOWN TO BE REACHABLE from NilPy today -- this is the sibling half of a fixed double case, filed so it is not rediscovered, not a measured failure. |
— |
| bug-n-reading-the-typeerror-a-unary-minus-raised-inside-a-def-segfaults | N | 55 | bug | def neg(v): return -v; try: neg('s') except TypeError as e: str(e) SEGFAULTS (print(e) too), while the same handler around a binary operator's TypeError (1 - v) prints its message. The exception is raised and caught (a handler that does not touch e runs); reading e is what dies. Pre-exists the 2026-09-16 pyvar_neg change (the old 0 - v rewrite segfaults identically on 669685aeacc9103c). |
— |
| bug-n-str-of-a-pascal-declared-exception-ignores-str-when-caught-as-a-base | N | 50 | bug | str(e) on an exception class declared in a Pascal unit dispatches str by the STATIC type of the except clause, not the runtime type: except URLError as e gives '<urlopen error boom>' and except Exception as e gives 'boom' for the same object. CPython gives the same string either way. Pure-NilPy classes are NOT affected. |
— |
| bug-n-struct-pack-with-a-computed-format-and-star-args-raises-typeerror | N | 50 | bug | > | — |
| bug-n-super-as-an-expression-fails-with-a-misleading-diagnostic | N | 55 | bug | return super().hi() (super() in expression position, documented as unsupported) is refused with error: Nil Python: annotate the type / too dynamic [a=22 b=8] reported at line 1 — a diagnostic that names neither the construct nor the right line. Also: B.__init__(self) for a second base is class method not found. |
— |
| bug-n-sys-exit-is-a-halt-so-no-handler-sees-it | N | 35 | bug | sys.exit lowers to pylib's pysys_exit, which ends the process with Halt, so a SystemExit never exists: except SystemExit, finally and with exit handlers do not run on an exit. The exit STATUS and the printed message follow CPython since 2026-09-19. Making it a raise needs two things beside it: NilPy's SystemExit derives from Exception (CPython: BaseException, so that except Exception: does not swallow an exit), and an UNCAUGHT SystemExit must exit with its status and print nothing else, where today's unhandled-exception path is emitted per backend and prints Unhandled exception:. |
— |
| bug-n-the-demo-leaks-16-mb-per-two-minutes-on-a-real-world-and-it-is-not-in-the-render-path | N | 70 | bug | lekkerzeilen leaks ~16 MB per two minutes on a real world, and the RENDER PATH IS RULED OUT | — |
| bug-n-the-dunder-subscript-arm-is-duplicated-verbatim-in-two-lvalue-parsers | N | 40 | bug | The ~60-line getitem/setitem subscript arm exists TWICE, character for character: compiler/pyparser.inc ~38087 and compiler/pasparser_lval.inc ~1290. Which one a NilPy statement reaches depends on which lvalue parser its statement path entered, so a fix applied to one and not the other silently leaves a shape behind. Both copies had to be edited to close the augmented-subscript ticket. | — |
| bug-n-the-hex-string-escape-emits-a-raw-byte-not-a-code-point | N | 60 | bug | '\\xNN' for NN >= 0x80 puts a RAW BYTE in the string instead of code point U+00NN, producing a malformed string: '\xe9' encodes to [233] not [195,169], and '\x80' reports len() == 0 with ord() raising TypeError. chr(233), '\u00e9' and a literal 'é' are all correct, so it is the \x escape specifically. |
— |
| bug-n-the-lazy-builtin-constructors-and-divmod-are-still-not-values | N | 25 | bug | > | — |
| bug-n-the-property-conflict-warning-misses-five-of-eight-conflicts-including-the-one-that-crashed | N | 45 | bug | > | — |
| bug-n-tk-got-files-are-invisible-to-testmgr-privatization | N | 40 | bug | The tk loop in test-nilpy spells its BINARIES by full path — that was the callbacks fix — but still captures output to $(TESTTMP)/$$src.got. make -n yields /tmp/$src.got, which testmgr's filename scan cannot match, so those three files are never privatized and two concurrent runs share them. Found by T's new lint, in the recipe whose earlier fix was believed complete. |
— |
| bug-n-tuple-unpacking-of-an-inline-tuple-does-not-unpack-iterable-values | N | 65 | bug | a, b = X(), Y() binds EVERY target to the whole right-hand list instead of unpacking it, when the values' type defines iter or getitem. The swap idiom p, q = q, p is hit. A NAMED right-hand side (a, b = tup), a call (a, b = f()) and for-loop targets are all correct, and so is any class without iter/getitem -- so it takes a container-ish class AND an inline tuple display to trigger. Silent: downstream sees a list, and a longer program segfaults. |
— |
| bug-n-two-node-consumers-know-an-call-but-not-its-virtual-sibling | N | 40 | bug | Found by inspection, NOT reproduced: NodeEnumIdOf's call arm and PyEvalOnce's chained-receiver test both match AN_CALL without AN_VIRTUAL_CALL, so a VIRTUAL method call loses its enum result identity and a chained call receiver is re-evaluated per link. Both predate the dunder-dispatch fix that surfaced them. | — |
| bug-n-two-return-type-inference-passes-answer-different-kinds-for-one-def | N | 35 | bug | > | — |
| bug-n-two-same-named-defs-in-exclusive-branches-of-one-function-collapse-silently | N | 60 | bug | if flag: def pick(): return 7 / else: def pick(): return 9 inside ONE function answers 9 for BOTH branches -- CPython gives 7 and 9. SILENT, exit 0, no diagnostic. Two same-named nested defs in one function collapse to one proc and the call resolves by POSITION, so a call after the if/else takes the LAST definition whatever ran. It is loud only when the arities DIFFER (no overload of outer.pick$29 matches), which is the already-closed method-collision case; when they MATCH -- the idiomatic shape, since exclusive branches naturally define the same signature -- it is a wrong value. Sequential rebinding in one function is CORRECT (def pick ... def pick answers 7 then 9), so this is specific to defs the program treats as alternatives. DISTINCT from bug-nilpy-same-named-nested-defs-in-two-methods-collide (two METHODS, loud, done) whose own note records that two plain FUNCTIONS do not collide -- one function was never tested. Cousin of bug-n-a-def-inside-a-taken-branch-does-not-rebind-the-name at module level, where the same source shape is REFUSED instead: conditional definition is broken at both scopes, differently. |
— |
| bug-n-type-of-a-member-read-on-a-bare-receiver-jumps-through-a-null-pointer-in-the-lekkerzeilen-demo | N | 45 | bug | A BARE READ of a method as a value off an unannotated receiver -- m = env.current, no call -- segfaults with PC 0x0 in the lekkerzeilen demo, an indirect call whose callee address was never filled in. THE OPERATOR IS THE READ, NOT type(): the original probe bundled a read, a type() call and an name fetch into one expression, and split into four markers it dies before type() is reached, so the old title and the no isolated repro finding were both measured against the wrong operator. Cause UNKNOWN; three candidates are eliminated by measurement -- blocker 03's name collision, ASLR, and the module-scan normalisation bug whose fix clears an identical-looking sibling and does nothing here. Demo-only, one variable. |
— |
| bug-n-typeinfo-reads-the-wrong-token-and-switches-on-kind | N | 45 | bug | NilPy's TypeInfo path carries the same two defects Track A just fixed on the Pascal side: it reads GetTokenStr(TokPos) — one token PAST the type name, because Next already advanced — and it resolves the type from the TOKEN KIND rather than the spelling, so TypeInfo(byte) answers Integer (byte and integer share tkInteger_T). | — |
| bug-n-unary-dunders-do-not-dispatch-on-a-variant-operand | N | 55 | bug | -v, ~v and abs(v) on a user class RAISE when the operand is a variant |
— |
| bug-n-yield-from-is-not-implemented | N | 65 | bug | yield from is refused -- undefined variable (from), i.e. the lexer never sees it as one construct. It is Python 3.3, predates every feature NilPy does implement, and generator delegation is ordinary code in working CPython programs, so by NilPy's upward-compatibility charter it is a bug rather than a divergence. Found by the feature probe that produced the sys.version_info ruling. |
— |
| bug-nilpy-a-generator-instance-leaks-its-locals-and-argument-cells | N | 35→75 | bug | Re-measured 2026-09-04 at 7e271ff7d: TWO leaks, one block each per generator INSTANCE, and they are independent. (1) a managed value in a persistent slot is dropped without release -- 1.0 blocks/generator with a class local and no argument; (2) each variant argument's 16-byte pycell_new cell is never freed -- 1.0 blocks/generator with an int argument and no managed local. Both together: 2.0. A generator with neither is FLAT (live=1), so the instance block and the yielded values are fine; it is exactly these two. One-off per instance, not per yield. | — |
| bug-nilpy-a-handler-binder-unwound-past-by-a-different-exception-still-leaks | N | 40 | bug | A NilPy except V as e: handler that binds X and is then unwound past by a DIFFERENT exception Y leaks X: 3.889 blocks per iteration, against 4.862 on pin v403 and a hypothetical ~0.9 if the pad released X. The unwind landing pad deliberately skips every handler binder, because it cannot tell X from an object that is IN FLIGHT (releasing that one is a use-after-free -- the SIGSEGV bug-nilpy-a-managed-local-in-an-unwound-frame-is-never-released fixed). The skip is the conservative half of a distinction the pad cannot currently make. STILL OPEN at cf8a5af93, which closed the sibling bound-arm leak; re-measured at 3.729/iteration. MEASURE IT WITH THE TRY INSIDE A FUNCTION -- a probe with the try in the module body now reads 0.000, because the sibling fix releases at re-execution of the try in the SAME frame, and that probe would read as fixed. |
bug-a-the-wasm32-scope-exit-release-loop-consults-neither-skip-predicate |
| bug-nilpy-a-python-override-of-a-virtual-pascal-method-segfaults-when-called-back-from-the-pascal-side | N | 65→68 | bug | MECHANISM FOUND 2026-09-21: a Python method overriding a Pascal virtual is installed in the vtable slot with NO ABI ADAPTER, and the two sides disagree about how a result comes back. The nilpy body returns a 16-byte Python value by writing it through a HIDDEN DESTINATION POINTER in %rdi; the Pascal slot is function optionxform(const s: AnsiString): AnsiString, which returns 8 bytes in %rax and passes no such pointer. So on any return path that needs a memory copy the body executes rep movsb of 16 bytes to %rdi = 0 and faults. Dispatch itself is correct -- right override, live self, correct argument. The condition that springs it is a virtual call originating in PASCAL code and landing in a NILPY method body; a nilpy->nilpy override and a Pascal->Pascal override are both fine, so neither the frontend's own tests nor the RTL's can reach it. Worked example is configparser.ConfigParser.optionxform, whose unit header (lib/rtl/configparser.pas:17-25) declares it virtual FOR THIS PATTERN and quotes the subclass verbatim -- the author explicitly guarded against the override 'silently never running' and the mechanism fails the other way instead. Not scoped to configparser: any virtual in a lib/rtl unit that a Python program may override has this shape, and the RTL deliberately marks such methods virtual, so the population is every one of them. Nine-line repro with a passing negative control below. Blocks feature-demo-songformatter-pxx-target (p68): settings.py compiles with two warnings and segfaults at module level, deterministically, while CPython runs it clean. |
— |
| bug-nilpy-augmented-repeat-on-a-variant-target-still-rebinds | N | 35 | bug | A dict VALUE as the *= target still rebinds, so an alias of it keeps the old contents. The parameter half landed 2026-08-15 (pymul_v_inplace); this is the residue, and += has the same split. |
— |
| bug-nilpy-calling-a-duplicated-ordinary-method-segfaults | N | 55 | bug | A class defining one ordinary method twice compiles, then SEGFAULTS when the method is called. CPython rebinds and the last definition wins. Pre-existing — identical on the pre-fix pinned binary and on the fix for the duplicate-method HANG, so the two are different defects sharing one source shape. | — |
| bug-nilpy-classmethod-constructors-on-builtin-types-are-absent | N | 25 | bug | bytes.fromhex(\"6162\") and float.fromhex(\"0x1p3\") are undefined variable (bytes) / (float) — the TYPE used as a namespace resolves only for the handful of names the stdlib table lists (int.from_bytes, dict.fromkeys, str.maketrans). |
— |
| bug-nilpy-del-on-a-plain-variable-silently-does-nothing | N | 35 | bug | NilPy: del x on a plain variable is accepted and does nothing — the name stays bound, so reading it afterwards returns the old value where CPython raises NameError. del lst[i] and del d[k] are correct. |
— |
| bug-nilpy-delattr-globals-and-locals-are-absent | N | 12 | bug | delattr, globals() and locals() are undefined variable. delattr is a real gap with no runtime entry behind it; globals/locals want a run-time name table this dialect deliberately does not build, so they may be a documented divergence rather than a bug. |
— |
| bug-nilpy-except-tuple-binder-is-typed-by-the-first-arm-only | N | 20 | bug | except (A, B) as e binds ONE variable typed as the FIRST listed class, so when B is caught its object is read at A's field offsets. Harmless inside the Python tree (every arm descends from PyException) and a SILENT WRONG VALUE the moment a tuple crosses hierarchies — measured: except (ValueError, su.Exception) as e prints an EMPTY message once the two classes' layouts differ by one field. |
— |
| bug-nilpy-four-remaining-absent-builtins | N | 12 | bug | The residue of the 2026-08-12 builtin sweep: slice, dir, vars, memoryview are undefined variable, and complex is a numeric TYPE this dialect does not have rather than a missing name. None has appeared in any corpus scan. |
— |
| bug-nilpy-songformatter-no-longer-compiles-set-callback-and-get-arity | N | 60 | bug | songformatter (the real CPython app) no longer compiles: set_ no such member on the scrollbar callback, and a get() arity error in settings.py — app unchanged since 2026-07-28 |
— |
| compat-n-repr-does-not-escape-non-printables-above-u007f | N | 15 | compat | repr() escapes only below U+0080, so C1 controls, NBSP and non-printable astral characters print raw where CPython escapes them: repr(chr(0x80)) is the raw byte here and '\x80' in CPython. Everything below 0x80 is already correct. Output FORMATTING of a non-float value, so compat at low prio by CLAUDE.md's table. |
— |
| feature-a-declaration-phase | N | 60 | feature | A real declaration phase: all decls before any body is typed | — |
| feature-n-a-call-cannot-unpack-a-sequence-into-its-arguments | N | 70 | feature | SEE ALSO feature-n-a-method-call-cannot-take-an-argument-after-a-star-unpack, filed 2026-09-11, which is the shape THIS ticket's matrix did not cover and is now the lekkerzeilen wall: an argument AFTER the star, which free functions accept and methods refuse. MOSTLY LANDED 2026-09-11 -- ONE SHAPE LEFT, and the original summary below was wrong about the scope. A 6-shape matrix showed five already worked before any fix; the real gap was an ARITY DEFERRAL on the runtime-dispatch path, fixed by PyCallHasStarArgAt (landed in 55981bc63). Re-measured at that commit against CPython: f(*xs), f(1,*xs), f(**d) and a statically-typed method's K().m(xs) all compile and all match. THE ONE REMAINING SHAPE is a star-arg on a DYNAMICALLY-TYPED receiver -- pick().m(*xs) where pick()'s return type is not statically known -- which is error: expected expression with the caret on the * itself, so the star is not parsed at all on that arm. That is a DIFFERENT site from the arity deferral and is what is left to do; it is also narrow enough that the corpus can route around it where the feature cannot. SUPERSEDED ORIGINAL, and its counts were wrong too -- the 59 came from a grep that matched COUNT() inside an SQL string and missed f(**d) entirely; recount with ast, not grep, before quoting a population: f(*seq) is error: expected expression. 59 call sites in lekkerzeilen's entry-point closure, 35 of them the mixed f(a, *seq) form and one f(**mapping); 38 of the 59 are in app.py alone. This is the wall the demo now stands at (app.py:626) after five earlier walls were cleared, and it is NOT patchable in the corpus the way the earlier ones were -- at 59 idiomatic sites the feature is cheaper than the edits. |
— |
| feature-n-a-keyword-after-a-star-unpack-at-a-construction-is-still-refused | N | 45 | feature | Cls(*xs, kw=v) is still refused with an argument after *unpacking is not supported yet, after the same shape was fixed for all three METHOD call sites. Deliberately left: PyClassCreate's argument loop resolves a keyword to a FIELD INDEX (kwFld := kwPk - 1), not to the 1-based parameter slot every method path uses, and it carries its own nArgs/kwAny/kwExtraHead bookkeeping that the star expansion does not feed -- so wiring the cap in the same way risks a plausible wrong argument where today it is a clean refusal. The cap mechanism itself already exists and works; only this site's bookkeeping is unwired. One-line change plus whatever nArgs needs. |
— |
| feature-n-a-kwargs-collecting-callee-through-a-callable-value | N | 55 | feature | A callee that collects **kwargs cannot be called through a callable value at all — every shape raises TypeError, including def f(a, **kw) called as zz(1) with no defaults anywhere. The dynamic bridge has no way to synthesize the empty dict the body expects in the collector slot, so the collector is deliberately left counted in ReqN to make the call REFUSE loudly rather than dispatch at an arity the body does not have. Split out of the *args fix; that half is done and CPython-exact. |
— |
| feature-n-a-pxx-marker-module-so-an-application-can-ask-whether-it-is-under-pxx | N | 45 | feature | OWNER'S DECISION 2026-09-20 (relayed by frankuser, secondhand): instead of an application detecting pxx by try: import ctypes FAILING, give pxx a special named module that exists ONLY under pxx, so the application asks the question directly. His words: "we could have PXX have a special named module (could be mostly empty) that would indicate if we are compiling under pxx. that way, we don't need the 'ctypes import' hack - and can safely implement a ctypes. small change in the lekkerzeilen and TSP application, and allows us to move forward." The mechanism already exists and needs no new machinery: NilPy resolves try: import X / except ImportError: at COMPILE time, and CPython takes the except arm naturally because the module is not there. BUYS ZERO UNITS TODAY AND THIS FIELD LED WITH A COUNT OF 10 UNTIL 2026-09-21. The count was true of a PREDICTED mechanism and the measurement went the other way, so the field acquired a dependency on the prediction holding; reported by frankz-e5 off tsp-compile-wall-inventory-2026-09-20.md's re-sweep, and re-verified here in TSP's own source rather than on report. THE MECHANISM, WHICH DOES NOT DECAY: the marker is NECESSARY for the pxx arm and NOT SUFFICIENT while _pxx_backend.py is absent. tsp/platform/init.py:98-109 is try: import __pxx__ / except ImportError: _ctypes_backend / else: from . import _pxx_backend -- so shipping the marker makes pxx take the ELSE arm and fail one line later on a module that does not exist (find _pxx* over the whole archive returns nothing). The wall moves; it does not clear. Same for lekkerzeilen, where the actual unblocker was the NESTED-GUARD bug, fixed separately, and the marker is a SIMPLIFICATION its seat can live without. WHAT WOULD RAISE THIS AGAIN: the parked ~1k-line _pxx_backend port landing, which is the owner's to unpark (tuxspaceprogram-c6, twice) and is not a ticket in this repo. PRIO DROPPED 80 -> 45 FOR THAT REASON: at 80 it was the top of the TSP queue and a seat pulling ready --track N was being sent to work that clears nothing. For TSP: _pxx_backend DOES NOT EXIST, so shipping the marker moves TSP from failing at ctypes to failing at _pxx_backend not found -- the real gate is a parked ~1k-line port and it is the owner's to unpark (tuxspaceprogram-c6, twice). For lekkerzeilen: the unblocker was the NESTED-GUARD bug, fixed separately, and the marker is a SIMPLIFICATION its seat can live without. So this ticket is WITHOUT pxx pretending ctypes is absent -- and it leaves room to implement a real ctypes later. TWO ENGINEERING POINTS ARE OPEN, both raised to the owner and neither settled: the module's NAME (a claimable name like pxx answers TRUE under CPython for anyone who pip-installs a package of that name; a dunder-ish name such as __pxx__ cannot be claimed -- and sys.implementation.name is where a CPython programmer looks first, with the module as the cheap check), and its CONTENT (he said "mostly empty"; a seat proposed target, pointer size and version, since applications have no way to ask today -- last week's sys.maxsize bug was that same gap). THE NAME IS __pxx__ -- HIS RULING, RELAYED, NOT HEARD FIRSTHAND BY THIS FIELD'S AUTHOR, 2026-09-20. The word RELAYED is inside the sentence a re-teller would quote, deliberately: on this exact question a headline has already detached from its body once today. Provenance in full, because provenance is what went wrong here once: relayed by frankuser, which reports three separate turns in his own pane -- he chose __pxx__ from options it put to him with that exact spelling shown; then, unprompted, "good. that's decided then. tell TSP the same"; then "yes" to telling the lekkerzeilen seat; and finally, when the question kept being relayed, "i already said pxx is just fine, 3 times now." THE EARLIER RETRACTION IN THIS FIELD WAS CORRECT WHEN MADE AND IS NOW COMPLETED, NOT CONTRADICTED. The first answer came off a seat's menu, which is exactly the shape a second source exists to catch; what was wrong afterwards is that the doubt outlived its own refutation -- he re-confirmed twice in his own words while the question went on being relayed. The PROPERTY he was answering is unchanged and is the reason the spelling is right: a third party must not be able to make the check come out TRUE on ordinary CPython by publishing a package of that name, so the spelling must be unclaimable. Anyone re-opening this should read his own pane, not this field. AND THE DETECTION IS LEXICAL, NOT A BOOLEAN: a module-level HAVE_PXX = True/False tested elsewhere is a RUNTIME condition and pxx compiles the ctypes arm anyway, defeating the whole mechanism -- the guarded import sits where the choice is made, with the selected name bound on both arms (worked example: TSP 98e9e66). RELATED AND POSSIBLY ONE FIX: devdocs/pxx-blockers/07-nested-import-guard-compiles-the-dead-arm/ (lekkerzeilen-7a, 5a7fc43) -- a FLAT import guard is correctly skipped and a NESTED one is COMPILED, so the only shape that works on both today's compiler and one that ships this module is refused today. Whoever takes this should have 07 in hand. |
— |
| feature-n-dataclass-frozen-true-needs-a-store-guard-not-an-acceptance | N | 40 | feature | MECHANISM: @dataclass(frozen=True) is refused by name, and the refusal is CORRECT as it stands -- PyParseDataclassArgs says in its own comment that silently accepting it would hand back a class that looks immutable and is not, a silent wrong answer in place of today's loud one. So the work is NOT deleting the refusal; the generated init/eq/repr are already what a frozen dataclass needs, and the ONLY thing frozen adds that we lack is that a store to a field must RAISE. THE HOLE A COMPILE-TIME GUARD LEAVES IS THE ONE TO DESIGN FOR: a store through a receiver whose class is statically known can be refused at compile time, but a store through a VARIANT-held instance cannot, and that is exactly the shape real code has (an instance pulled out of a dict or list is a variant here -- measured separately on shape.py). A guard that catches the easy half and silently permits the hard half is the same silent-wrong-answer the current refusal exists to prevent, so the acceptance should land WITH a runtime store check, not before it. Second-order: frozen also makes a dataclass HASHABLE in CPython (eq=True, frozen=True generates hash), so a program may legitimately use one as a dict key or set member -- accepting frozen without that is a different silent gap. |
— |
| feature-n-dataclasses-replace-and-copy-copy-need-one-capability-per-object-field-enumeration | N | 45 | feature | MECHANISM: dataclasses.replace(obj, **changes) returns a NEW instance of obj's class with the named fields replaced, so it needs (a) the ability to make an instance of a class known only at run time and (b) the ability to enumerate that instance's fields in order. NilPy has NEITHER, and the same two are what copy.copy of an arbitrary object needs -- mimic_copy.py already refuses it in those words ('copying an arbitrary object needs copy / reduce_ex introspection that NilPy does not have'). So these are ONE capability wearing two module names, and implementing either separately would build half of it twice. WHAT IS NOT MISSING, measured, and it is what makes this look easier than it is: the dynamic ATTRIBUTE GET already works on a variant-held instance -- sol.name reads correctly -- so a per-object field table exists for lookup BY NAME. It is ENUMERATION and ALLOCATION that are absent. A PARSE-TIME DESUGAR IS NOT A FIX FOR THE REAL PROGRAM, and this is the measurement to keep: the obvious implementation builds a constructor call from the statically known class, and BOTH real call sites (tuxspaceprogram/shape.py:115 and :135) take their receiver out of a dict value inside a comprehension, where the class is a variant -- self.by_part is a TPyDict and the element class is not carried. A fixture with a statically typed receiver would pass while the program that motivated the work still walls. |
— |
| feature-n-from-accepts-a-quoted-foreign-file | N | 45 | feature | from 'basehook.pas' import ConfigBase and from 'basehook.pas' as bh import X, Y are refused with "expected a module name after from", while import 'basehook.pas' as bh works. Both from-arms test tkIdent and never consider tkString. The semantics already exist — from-import discards its name list and importing a unit opens its namespace flat — so this is a parser change with no new resolution path. |
— |
| feature-n-nilpy-ast-typing-module-scope | N | 20 | feature | NilPy: type MODULE locals from the AST too | — |
| feature-n-nilpy-has-no-del-and-its-absence-is-load-bearing-in-an-open-fork | N | 35 | feature | grep -rn __del__ compiler/ lib/ test/ is EMPTY at da2fea0fd — no lexer token, no parser arm, no runtime call, no test, and no entry in nilpy-semantics-divergences.md, so it is an unrecorded gap rather than a chosen divergence. That is a hole in an otherwise near-complete protocol family: init, enter/exit, iter/next, getattr, getitem/setitem/delitem, call, bool, len, contains, repr/str, index, every arithmetic operator with its reflected and in-place forms, and the six comparisons are all present. THE REASON IT IS NOT MERELY MISSING: decide-a-how-should-the-nilpy-managed-finalize-re-enter-the-heap-lock argues option (b) -- defer the nested release -- on the ground that its observable finalizer-ORDERING change is 'a cost against a feature nobody has built'. That is true today and it stops being true the moment this lands, so implementing del under (b) reintroduces exactly the cost (b) was costed as not having. Under (a), a reentrant lock, a user finalizer can allocate and the ordering question does not arise. So this is not independent work: it should be built on whichever arm the owner picks, and it is an argument for (a). Siblings, also absent and also unrecorded, filed here as a note rather than as tickets: new, slots, format. |
feature-a-make-the-heap-lock-reentrant |
| feature-n-nilpy-has-no-reachable-path-to-the-sys-and-arg-intrinsics | N | 20 | feature | NilPy cannot reach sysopen/sysread/syswrite/argcount/argstr as INTRINSICS, and has not been able to for as long as anyone has measured. PyParseFactorCore held five case arms matching those as TOKENS, and every -Ord(tkXxx) construction site in pyparser.inc was inside them — so the arms were the only path, and the arms could not fire. Surfaced by deleting them (they went dead for good when 5f177b181 made the spellings soft keywords), which is the only reason this is visible at all: dead code was standing in for a missing capability. NOT a regression — nothing that used to work stopped. The open question is whether NilPy should have these at all, given a NilPy program can already declare and bind its own paramstr/paramcount (frankD measured exactly that), and Python's own idiom is sys.argv rather than a paramstr intrinsic. | — |
| feature-n-random-random-has-no-per-instance-rng-class-and-the-state-machinery-already-exists | N | 45 | feature | random.Random(seed) has no per-instance RNG — TSP's smoke.py and commentary.py each want one so a run is reproducible from a seed without disturbing the global stream. The per-instance STATE already exists and is complete: lib/rtl/random.pas has TRandomState with Seed/Randomize/Next/Range/Range64/Double/Bytes/Split. What is missing is a way to REACH it: plain import random is consumed-only, so random is not bound to its unit and random.Random cannot resolve to a class there. The silent half of this is already fixed separately — random.Random used to fold onto random.random and evaluate to a float; it is now loud. This ticket is only the feature. |
— |
| feature-n-register-every-module-s-classes-before-any-module-s-methods-are-typed | N | 75 | feature | Call-site parameter typing sees every module's SITES (the import closure is lexed first), but classes are still registered one module at a time, so a field of a class in a later module (sim.py's self.torque, a Vec3) is unknown when an earlier module's method parameter (math3d.py's Quat.rotate v) is asked and memoised. Register every module's classes -- names, fields, method signatures -- over the whole closure before any module's methods are typed; then class-typed fields feed sites and the demo's Quat.rotate gate (P16/P17, 11 of 14 sites already provably Vec3) becomes reachable. | — |
| feature-n-register-the-class-shells-of-the-import-closure-before-parsing-any-body | N | 55 | feature | NilPy resolves a method on an unannotated receiver against the classes registered SO FAR, so a class in a module imported later is invisible and the call falls back to runtime dispatch with a warning; registering the import closure's class shells before any body is parsed would make the resolution order-independent | — |
| feature-n-route-pypal-through-wasi-imports-so-nilpy-can-do-file-io-on-wasm32 | N | 25 | feature | pypal on wasm32 returns a defined -1 from every entry point rather than trapping (the ESP precedent), which is what made NilPy compile for that target at all. It is not real file I/O: open fails, os.listdir is empty, time.time() raises. wasi preview1 HAS open/read/write/close/seek/getcwd/unlink/rename/readlink as imports, and lib/rtl/platform/wasi already binds them for the Pascal RTL -- so the work is a pypal backend that calls those imports, not new capability. ppoll is the one that does not map. |
— |
| feature-n-specialise-a-dunder-body-on-the-operand-type-the-call-site-already-knows | N | 80 | feature | An attribute read on a receiver whose class is not statically known runs the FULL dynamic protocol — a field read goes from 0 calls to 8, building a string key and probing a hash table. The call site usually knows the type already. Measured on real code: annotating the operand took Quat.rotate from 24674 B / 864 calls / 0 SSE to 2175 B / 18 calls / 30 SSE, and zeroed pydynattr_get_v in all three hot methods. Dunder operands are the commonest instance, but the rule is the receiver, not the dunder. | — |
| feature-n-subprocess-run-has-no-cwd-parameter-and-adding-one-is-a-pal-signature-change | N | 40 | feature | subprocess.run(argv, cwd=...) refuses with run has no parameter named 'cwd' — TSP's menu.py uses it twice. The MECHANISM is one line in the child and the COST is the signature: cwd has to travel through run, Popen.Create, PalVforkAndExec and all three PAL backends (posix, esp, wasi), interface and implementation. Characterised, not started. The load-bearing correction to the first reading: PalBackendVforkAndExec does a REAL fork despite its name, and the child runs a Pascal path before execve, so the chdir belongs in the CHILD and there is no thread-safety hazard — the parent-chdir-and-restore hack that a reader would reach for first is both unnecessary and wrong. |
— |
| feature-n-sys-version-info-implementation-and-the-probe-suite | N | 62 | feature | Implement sys.version_info / version / hexversion at (3, 9, 0, 'final', 0) plus sys.implementation carrying NilPy's own identity, per the owner's ruling. All four read ONE constant. The number is a compatibility affordance and must be backed by a probe suite that fails when it stops being true -- the same feature probes that produced the ruling. | — |
| feature-nilpy-a-genexpr-is-lazy-not-materialised | N | 30 | feature | A genexpr's elements are built EAGERLY and then walked by a cursor, so single consumption is right but an INFINITE genexpr still cannot be expressed and side effects all happen at construction. True laziness means a TPyIter whose mapping is the element expression. | — |
| feature-nilpy-ascii-flag-fast-path | N | 25 | feature | Make pystr_isascii O(1) by reading PXX_FLAG_ASCII — but first MEASURE whether every string reaching it carries a header, because a false positive there is a silent wrong answer on exactly the non-ASCII strings the character surface exists for | — |
| feature-nilpy-collections-and-string-methods | N | 30 | feature | NilPy: list / dict + string methods (split/join/strip) | — |
| feature-nilpy-counter-api-beyond-the-constructor | N | 35 | feature | collections.Counter counts and reads correctly now, but three ordinary CPython spellings are missing: Counter({...}) (no dict overload — a COMPILE error listing the three that exist), .elements() (AttributeError), and Counter arithmetic c1 - c2 / c1 + c2 (TypeError). All three wall LOUDLY, which is the right failure mode, so this is a feature gap and not a bug. |
— |
| feature-nilpy-cpyext-cycle-collector | N | 30 | feature | cpyext: a cycle collector for the extension object model | — |
| feature-nilpy-fstring-nested-spec-and-nested-fstring | N | 30 | feature | f-string: a nested format spec and a nested f-string | — |
| feature-nilpy-hasattr-per-instance-assigned-tracking | N | 45 | feature | hasattr reports True for a field the instance never assigned — if flag: self.m = 1 then hasattr(a,"m") on a False path answers True where CPython answers False. The remaining half of the DECIDED decide-nilpy-hasattr-per-instance-semantics: the per-instance assigned bit. |
— |
| feature-nilpy-hoist-constant-container-literals-out-of-a-loop-condition | N | 25 | feature | NilPy: while x in (\"a\",\"b\") now rebuilds the constant tuple on every test. A provably-constant container build is loop-invariant and should be hoisted to a variable once — what a person would write by hand — while everything else keeps being folded into the condition. |
— |
| feature-nilpy-idf-import | N | 20 | feature | North-star integration milestone: nilpy source that includes an ARBITRARY ESP-IDF header and calls what it declares, with no hand-written per-API binding. BOTH stated blockers are now in done/ (feature-c-source-frontend, feature-esp32-idf-xtensa) -- the body's Blocked-by line is pre-YAML prose the ranker never saw, and progress.sh check --strict has been reporting it as STALE-EDGE-CLEAR. PROBED 2026-09-02 against an IDF-SHAPED header on the host (no ESP, no IDF checkout): extern calls, object-like macro constants, static-inline bodies, and a static inline whose body uses a function-like macro ALL work from nilpy today. The one measured gap is calling a FUNCTION-LIKE MACRO from nilpy source, which is RegisterCMacroConsts's documented limitation. So Slices A-C are effectively done for this path and Slice E is the live dependency. Full acceptance still needs an IDF checkout and ESP32-S3 hardware, neither of which is on this box. |
feature-c-source-frontend, feature-esp32-idf-xtensa |
| feature-nilpy-lambda-compiled-closure | N | 55 | feature | nilpy: lambdas are interpreted by pyeval — compile them like nested defs (perf + one semantics) | — |
| feature-nilpy-map-over-several-iterables | N | 40 | feature | map(f, xs, ys) — CPython's N-iterable map — is a PARSE error ("Expected: )"). The map arm reads exactly two arguments, and the whole callback path below it (PyCallKey1, pymap_iter_i, pyiter_map_i) is one-argument by construction. |
— |
| feature-nilpy-match-statement | N | 25 | feature | match / case — structural pattern matching is not parsed |
— |
| feature-nilpy-math-module-twelve-absent-names-measured | N | 30 | feature | THREE OF THE FOUR EXACT NAMES LANDED 2026-09-19 -- frexp, isqrt, isfinite as frontend intercepts (pymath_*), ldexp already resolved -- and the WHOLE SURFACE was swept name by name against CPython rather than sampled. Before: 40 agree / 18 absent / 3 differing. After: 43 agree / 15 absent / 3 differing. THE THREE DIFFERING ARE 1-2 ULP (cos 1, expm1 1, sinh 2) AND ARE NOT DEFECTS -- the F-lane rule is explicit that a float compared byte-exact against an oracle reddens for no defect, and quantifying them further is the attention failure that rule exists to name. THE COUNT IN THIS TICKET'S OLD TITLE WAS NEVER WRONG, IT WAS ORACLE-DEPENDENT: the surface is 62 names against CPython 3.14.4 and was 51 when this was filed, so 'twelve absent' and 'eighteen absent' are the same measurement against different Pythons. Any future count here needs the oracle version beside it. THE CARRIER QUESTION, WHICH IS WHY THE SWEEP WAS RUN AT ALL, IS ANSWERED AND THE ANSWER IS A NULL RESULT: a NilPy import math binds the same-named PASCAL unit and resolves case-insensitively, so a Python spelling reaches a Pascal routine whenever one bears the name -- and across 74 outcome comparisons (the full 62-name math surface plus 12 shared names spanning zlib, base64, html, json and re) there are ZERO silent VALUE divergences. Every failure was LOUD: a compile-time refusal. That is the mechanism being safe in the way that matters, because where the Pascal signature does not match Python's the compiler REFUSES rather than answering wrongly. So there is no bug to file against the binding. What it cost was one confusing DIAGNOSTIC -- math.frexp(8.0) said 'no overload of frexp matches these arguments', a Pascal sentence for someone who wrote Python -- and that is now moot, because the intercept means the Pascal routine is no longer reached. THE RESIDUAL HAZARD IS OWNED BY ANOTHER TICKET AND IS DELIBERATELY NOT RESTATED HERE: math.ldexp agreed BY LUCK rather than by design, and the reason plus what would turn that luck into a silent wrong value lives in the SUMMARY of bug-n-a-same-named-rtl-unit-shadows-both-a-relative-import-and-a-mimic-shim, which already held the mechanism and now holds the reason. One home, because a hazard split across three tickets is a hazard with no population. THE FIFTEEN STILL ABSENT (acosh asinh atanh cbrt dist erf erfc exp2 gamma lgamma log1p nextafter remainder sumprod ulp) inherit the standing 'do not map a 1-ulp-off RTL routine' policy and are what remains of this ticket. |
— |
| feature-nilpy-methods-on-int-and-float | N | 45 | feature | No methods on int or float — x.bit_length(), x.is_integer(), x.hex() |
— |
| feature-nilpy-multi-arg-callback-bridges | N | 45 | feature | nilpy runtime: pycallback_call2/3 and a multi-parameter bound-fn call, so a callable can receive more than one own argument | — |
| feature-nilpy-nested-def-as-value | N | 5 | feature | SUPERSEDED: nested def as a VALUE (stored, passed, returned) | — |
| feature-nilpy-no-type-inference-switch | N | 55 | feature | --no-type-inference: compile a NilPy program fully dynamically |
— |
| feature-nilpy-parallel-for-in | N | 10 | feature | NilPy parallel for-in — lower a marked for-loop to the shared PXXParallelFor runtime | — |
| feature-nilpy-parallel-reduction-bigint | N | 5 | feature | Opt-in arbitrary-precision reduction for parallel for. v1 keeps per-worker partials in the promo-int inline tier and raises at the spill point; this adds the real bignum path, which is correct but anti-scales because every bignum op takes the global heap spinlock. |
feature-nilpy-parallel-for-in |
| feature-nilpy-process-exec-binding | N | 60 | feature | nilpy: os.system / subprocess-shaped process spawning over the RTL's libc-free execve | — |
| feature-nilpy-small-syntax-gaps-found-by-the-2026-08-06-sweep | N | 58 | feature | Ordinary Python forms NilPy diagnoses cleanly but does not accept. print(sep=) and str.format() with 3+ (and 0) placeholders are DONE (2026-08-08); ten rows remain: enumerate(str), type(x) other than .name, a non-name lambda default, dict(x=1), .update(b=2), extended-slice assign, self.class.name, nested unpacking, bare tuple, two-for comprehension | — |
| feature-nilpy-str-format-named-keyword-fields | N | 55 | feature | "{name} is {age}".format(name=..., age=...) — named fields not supported |
— |
| feature-nilpy-str-surface-gaps-2026-08-09 | N | 40 | feature | str/bytes surface gaps found by the 2026-08-09 differential sweep | — |
| feature-nilpy-threadsafe-containers | N | 45 | feature | TPyList/TPyDict corrupt under concurrent mutation — append is a read-modify-write over a buffer PyListGrow may realloc, so two threads can use-after-free. Free-threaded CPython guarantees this cannot happen; adopt that contract under --threadsafe with one-way biased sharing. | — |
| feature-nilpy-tkinter-surface-vs-a-real-application | N | 60 | feature | The tkinter façade is built and now genuinely gated (it runs under Xvfb), but its widget/option surface has never been proven against a real application. songformatter's GUI is the forcing target: tkinter.font metrics (descent/measure), Canvas.create_text anchoring, Notebook, PanedWindow. Measurable for the first time now that a running harness exists. | — |
| feature-nilpy-walrus-operator | N | 35 | feature | := (walrus) — the assignment expression is not parsed |
— |
| perf-n-an-imported-npy-module-costs-13x-per-function-versus-the-same-code-inline | N | 60 | perf | MECHANISM, and it is the thing to look for again: a routine that scans a WHOLE-PROGRAM array once per DEFINITION, where that array holds every imported module concatenated -- so per-definition work scales with the import closure and an inline arm never pays it. It SPRINGS wherever a new per-definition or per-lookup scan is added over Tokens or UCls; it is not tied to any routine named here. THREE instances found and fixed, all landed and carried by pin v415: PyDefSiteMode's backward walk (now a precomputed enclosing-construct table), PyDefUsedAsValue's allocating CaseEqual(GetTokenStr(j),nm) per identifier token (now non-allocating TokenCaseEqual, length reject first, 62 sites) -- those two together MEASURED 12.4% -- and FindUClass's flat class-table scan, three scans per call with no early exit on the first, 4.31 BILLION true steps on lekkerzeilen (now a name-keyed hash index preserving the ranking exactly, d5de02143). INDEX MEASURED: 38.72% on lekkerzeilen (min-of-5, pin v414 aeadb1754b80 vs 94fddf62ee6af731 = pin v415, arms sha-pinned, outputs byte-identical) and 28% on uforth (frankh-c0, 131x step reduction). It GENERALISES -- an earlier +0.0% cross-corpus null was a stale arm built before the commit existed and is retracted. The ticket's own retirement condition (an interleaved min-of-N on lekkerzeilen with load recorded) IS NOW MET. STILL OPEN AND WHY THIS IS NOT CLOSED: the structural one-pass version is available and unbuilt for the scans that remain. A FOURTH AND FIFTH INSTANCE were found 2026-09-22 and NEITHER WAS IN THE CENSUS ABOVE -- PyClsAttrWriteScan and PyDynAttrEverAssigned scan from j := 1, and that census filtered on loops starting at 0, so the blind spot was the START VALUE and not the bound spelling this ticket named. MEASURED AND DELIBERATELY NOT BUILT: PyClsAttrWriteScan is 165 calls / 47,776,271 token visits on lekkerzeilen and removing it outright is worth 4.1% of the build (min-of-3 interleaved, base 62.93 s vs off 60.35 s, with a combined-define control reporting visits=0); PyDynAttrEverAssigned is called ZERO times there. The one-line if classW and instW then Break is semantically exact and worth NOTHING -- identical visit count, byte-identical output -- so only the table captures the 4.1%, which does not justify ~200 lines in the routine that decides class-attribute lowering at this ticket's rank. TWO RETRACTIONS BY THE SAME HAND, both in devdocs/perf/lekkerzeilen-build-time.md: 1.1% was reported off round 1 of a 3-round sweep (per-round 0.71/3.03/2.58) and read as a null; and the explanation offered for the small number -- that lekkerzeilen's build is not parse-dominated -- is FALSE and refuted by this ticket's own 38.72% FindUClass row. The real reason is magnitude alone: 4.31e9 steps against 4.78e7, 90x fewer for 9.4x less time. THE 13x HAS NOW BEEN RE-MEASURED (2026-09-22, franks-5b) AND IT IS NOT GONE: IT IS 6.2x. Same method as the original (identical bodies inline vs moved into one imported module), min-of-3 INTERLEAVED, compiler 734d10ec7b53 at cbb8f81c0, CWD repo root, load ~4 and stable across the run: 100 fns 2.30 vs 3.25 s, 200 fns 2.50 vs 4.52 s, 400 fns 2.93 vs 7.13 s. Per function off the 100->400 span, inline 2.10 ms and imported 12.93 ms, ratio 6.2x against the original 3.4/45 ms = 13.2x; the implied fixed cost lands at 2.09 s and 1.96 s for the two arms, which is the check that the slope is real rather than a fixed-term artefact. CARRY BOTH ROWS, DO NOT SUBTRACT THEM: the 13.2x was taken in another session at an unrecorded load, so the per-function MILLISECONDS are not comparable across the two and only the within-session RATIOS are -- each arm pair was interleaved, so shared load divides out of a ratio and does not divide out of a duration. What is safe to say: the ratio more than halved and the observable survives. Method, both refuted hypotheses, the retraction, and two harness faults of opposite sign: devdocs/perf/lekkerzeilen-build-time.md. |
— |
| perf-nilpy-remaining-perbyte-string-builders | N | 40 | perf | NilPy: remaining pylib string builders still append per-byte (O(n²)) | — |
| refactor-n-the-field-type-pre-pass-asks-one-question-in-six-places | N | 45 | refactor | MEASURED, not asserted: PyInferFieldDecl is 13 decision arms, and six of them -- 341 lines across PyHeaderParamType, PyModuleGlobalLiteralType, PyModuleGlobalCtorClass, PyModuleGlobalIsDef, PyMethodBindsLocal and PyRhsOnlyNamesThisMethodBinds -- ask ONE question in three scopes: where is this NAME bound and what is its initialiser. Three of them (the module-global trio) carried a BYTE-IDENTICAL copy of one scan differing only in the pattern matched at a statement head and the return type; those are now collapsed to one scan with no answer changed. The remaining duplication is the same question in the PARAMETER and METHOD-LOCAL scopes. Root cause: the pre-pass has no name environment because PyLocals is one FLAT table with a linear name scan and no scope field, and seeding it early was tried and reverted. | — |
| refactor-n-two-import-handlers-are-twins | N | 45 | refactor | PyParseOneImport (105 lines, 1 caller) and PyParseImportRun (283 lines, 4 callers) are two handlers for one concept — the tree already calls them 'the twin list' and 'the twin site'. The duplication is not cosmetic: it is why a relative import fails with two DIFFERENT errors depending on which one it reaches, and why fixing it has an ordering constraint at all. | — |
| refactor-n-user-class-dunders-are-dispatched-at-run-time-when-the-left-operand-is-static | N | 30 | refactor | > | — |
| refactor-nilpy-three-places-decide-a-locals-class-identity | N | 40 | refactor | Three separate places decide a NilPy local's class identity | — |
backlog-tools (66)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| bug-t-177-slug-citations-in-compiler-and-lib-comments-resolve-to-no-ticket | T | 45 | bug | A CENSUS IS NOT A FINDING, AND THAT IS THE TICKET. A citation that resolves to nothing cannot be checked off, so the ticket it fixes STAYS OPEN and gets worked twice — measured: frankB re-diagnosed and nearly re-fixed a bug fixed six hours earlier because compiler/pylexer.inc:1561 cited a slug that never existed. MY CENSUS CAUGHT THAT ROW AND IT DID NOT HELP: it was ROW 38 OF 182 and nobody read row 38, which retires the baseline design on its own evidence and argues FORWARD-ONLY. ~181 rows resolve to nothing of 2324 (2109 resolve, the positive control), but the count is not the deliverable and is not reproducible — this ticket said 177, a reimplementation from its own prose said 195, and EIGHT corrections later it is 181. Controls must be SYNTHETIC: both real rows named here went stale within 90 minutes. tools/slug_citation_census.py is the method, wired into nothing. | — |
| bug-t-25-of-56-make-test-targets-are-reachable-from-no-tier | T | 45 | bug | 25 of the Makefile's 56 test-* targets are reachable from NO tier root in tools/testmgr.py -- not named in quick/native/limited/full, and not a prerequisite (transitively) of anything that is. They run only when somebody types them. The list MIXES two populations that must not be closed together: targets that are deliberately manual because they need a toolchain this fleet does not have (test-esp-idf, test-fpc, test-sqlite-*), and targets that were simply never enrolled -- including three cross-target gates written in the last week (test-record-layout-cross-frontend, test-skeleton-frontends-cross-target, test-packrecords-c-gcc-oracle). This is the same enrolment hole test-nilpy was in (238 of 309 .npy files invisible to the watcher, 2026-08-01) and test-uforth after it, both recorded in testmgr.py's own comments -- so the hole is KNOWN to recur and nothing measures it. |
— |
| bug-t-a-backgrounded-tier-reports-the-wrappers-exit-code-over-the-tiers-verdict | T | 55 | bug | A backgrounded gate.sh/testmgr run reports exit code 0 in its completion notification while its own log says testmgr: RED / gate: RED (exit 1). SEVEN independent sightings across at least three sessions since 2026-09-02. The notification is not wrong about anything — it reports the WRAPPER's exit status, and the wrapper succeeded at running the tier. It is read as the tier's verdict, because that is the only number a completion notification usually carries. CLAUDE.md already tells every agent to grep the log instead, which is a documented workaround for a live defect, not a fix. |
— |
| bug-t-a-close-publish-dies-when-the-clones-local-branch-lacks-the-stub-being-closed | T | 30 | bug | MEASURED 2026-09-21 in a scratch clone, found while fixing the auto-close duplicate: if a stub was filed on origin AFTER this clone's last publish, the clone tests DETACHED at a sha that has the ticket, reads it, unlinks it — and then Clone.publish() checks out the local branch, which never had the file, so git add -- <backlog path> exits 128 with fatal: pathspec ... did not match any files and the RuntimeError aborts the whole close. Distinct from the duplicate bug (fixed at the sibling ticket): that one loses a deletion silently, this one is loud and takes the cycle's other closes with it. NOT reproduced in production and no evidence it has fired — a single live watcher host (borg) publishes often enough that its branch tracks origin closely, so the window needs a second filer. Repro is four commands in a scratch repo; it needs a decision about publish()'s contract rather than a local patch, which is why it is filed and not fixed. |
— |
| bug-t-a-commit-made-in-the-watcher-clone-during-a-gate-is-unreachable-from-any-ref | T | 50 | bug | The Track T watcher clone sits on a DETACHED HEAD whenever the daemon is mid-gate, because it checks out the sha under test. A commit made in that window is not merely unpushed -- it is unreachable from any ref (git for-each-ref --contains <sha> returns nothing) and one git gc from gone. Measured 2026-09-06 on seven: 038c3acf1 survived because the clone happened to be on master ([ahead 2]); 3815bee43, same clone, same session, 90 minutes later, was born parentless of any branch. The repo's existing rule (A LOCAL COMMIT IS NOT BANKING) does not cover this, because the mitigation it prescribes -- remember to push -- never fires if you do not notice you were detached, and git log --oneline -1 looks normal. The tell is one line: git status -sb printing ## HEAD (no branch). |
— |
| bug-t-a-gate-red-does-not-say-whether-it-is-yours-or-the-trees | T | 45 | bug | A gate.sh FAIL row does not say whether the failure is one THIS seat just introduced or one that was already on the tree. Those two call for opposite actions — fix it now, versus check who is already on it — and they print the identical line. Measured 2026-09-06: fecdfe6dc landed a silent Makefile assertion at 19:25:54 and within roughly the next hour FOUR seats found it independently (frankB fixed it; frankD fixed the same line minutes later and lost the commit as empty on the rebase; frankA found and fixed the sibling neither had covered, 5c327fbaa; frankS reported it a fourth time). Three duplicated efforts and one rebase. gate.sh's OUTPUT is not the defect — it names the check, the file and the line, and publishes gate: RED (exit 1). What is missing is provenance. |
— |
| bug-t-a-negative-test-row-cannot-say-which-way-it-flipped | T | 45 | bug | In test-core's fail-fast recipe a *_fail row is ! ./$(COMPILER) ... on one line and a grep -q for the expected message on the next, so THREE outcomes collapse into one indistinguishable failure: refused for the WRONG reason (fails at the grep), ACCEPTED (fails at the !), and the compiler CRASHED (fails at the !). All three read as 'the recipe stopped here'. Measured consequence, 2026-09-05: a *_fail test whose refusal had been deliberately lifted by its own feature commit sat at STEP 6 OF 15 and silently cost four fifths of the tier -- 3783 lines against 15253 once removed. The fix is to make the pair ATOMIC, not to add a helper. Positive control is available in the file: three shapes -- refused correctly, refused wrongly, accepted -- must produce three distinguishable verdicts. |
— |
| bug-t-a-probe-that-exits-2-to-say-its-instrument-is-broken-is-published-as-a-compiler-red | T | 55 | bug | The harness has no channel for INSTRUMENT-UNAVAILABLE, so a probe that exits 2 specifically to say 'I could not measure' is published identically to a probe that exits 1 to say 'pxx is wrong'. tools/aarch64_cabi_prologue_probe.sh separates the two codes deliberately and documents why (exit 0 would be laundering, per done/bug-t-tstate-launders-skip-into-pass) -- and nothing downstream reads the distinction, so it arrived in a full tier as a cross-target compiler regression that cost a ticket and a bisect to discharge. THIS IS THE MIRROR of bug-t-a-recipe-that-self-skips-a-missing-oracle-is-not-counted-as-a-coverage-hole (p70), whose measured undercount is 'always DOWNWARD': an honest exit 0 loses a coverage hole, an honest exit 2 gains a false finding. ONE missing vocabulary, two directions. Do NOT fix it by making exit 2 quiet or by mapping it to SKIP -- a declared host limitation skips, a present-but-unusable instrument is undiagnosed, and the probe's own header rules both out. | — |
| bug-t-a-ranked-ticket-that-blocks-itself-in-prose-is-invisible-to-every-check | T | 50 | bug | STALE-PARK reads a ticket's PROSE for blocking phrases only inside unfinished/, blocked/ and working/ -- tools/progress.py:1867, park_scope = t.status in (\"unfinished\", \"blocked\", \"working\"). So a ticket sitting in a RANKED folder whose own body says do not start here is invisible to every check we have, and it keeps its rank. Two verified instances, both at the top of Track P's queue on the night of the campaign: a p55 whose body reads in bold Do not start here -- the choice made there decides how this is fixed and a p60 whose closing line reads The refactor is therefore NECESSARY and not sufficient for this bug; both had blocked-by: []. frank-optimize wired both (00555ab08, 27749fd01) and the ranker reacted exactly as designed -- the bug left ready and the refactor rose 55 -> 60 marked unblocks 1. THIS IS THE SECOND INSTANCE OF THE SAME APERTURE IN THE SAME FAMILY: the comment at progress.py:1844 records DANGLING-LINK inheriting this identical folder filter and reporting 0 findings while four live dangles sat in backlog/. That one was widened; STALE-PARK was not. |
— |
| bug-t-a-recipe-cannot-declare-its-own-skip-a-coverage-hole | T | 45 | bug | A Makefile recipe that self-skips has no way to say its skip is a COVERAGE HOLE. _self_skipped returns the recipe's whole SKIP line, which always begins <target>: SKIP, and SKIP_HOLE_PREFIXES matches at position 0 — so a recipe self-skip can never be counted as a hole, by construction and on purpose. That is right for a recipe guarding its own optional probe and wrong for test-zlib's gcc oracle not found, which is coverage the box is not providing on a job backing a public claim. Needs a channel from the recipe, NOT a looser match in the harness. |
— |
| bug-t-a-recipe-that-self-skips-a-missing-oracle-is-not-counted-as-a-coverage-hole | T | 70 | bug | 72 NOT verified sites in the Makefile print an honest sentence and exit 0, so a box missing a qemu or a multilib gcc runs a NARROWER tier and reports an IDENTICAL verdict. testmgr.py classifies SKIPs and publishes skip_holes into the pin manifest, but a recipe's OWN <target>: SKIP line is deliberately excluded (tools/testmgr.py:2812-2824) — so none of the 72 is counted. MEASURED 2026-09-12 on borg: two gcc -m32 oracles (Makefile:13986, 14147, 21430) had been silently not running; multilib was simply absent. The comment at :2812 already names this exact case as unsolved and RULES OUT the tempting fix (loosening the classifier match); it needs a CHANNEL from the recipe. Cited there too: the gtk undercount, five holes uncounted for seven weeks because an emitter said host tool absent: where the classifier looked for tool absent: — the same failure, and the undercount is always DOWNWARD. THIS ALSO MAKES skip_holes == 0 UNSOUND AS THE -O3 PROMOTION PROOF GATE. |
— |
| bug-t-a-restart-converts-owned-scratch-into-unowned-scratch-and-nothing-observes-it | T | 40 | bug | /tmp on plexus hit 99% (962M free of 94G); 45G of it was ONE orphaned session scratchpad holding 159,442 files, and deleting it returned the volume to 52%. The reported cause -- 'a benchmark harness that never deletes' -- does NOT reproduce: no committed tool in this repo emits ab.a.<pid>.bin/.map, so there is no harness to fix and the 'it will refill in about a week' prediction has no mechanism behind it. The actual defect is that a RESTART converts owned scratch into unowned scratch instantly, with no ceiling, no reaper and no owner, and nothing observes the transition -- the 45G was legitimate live scratch until the session that owned it stopped existing. Post-cleanup there are ZERO orphans: all 15.6G remaining was written today by live sessions. |
— |
| bug-t-a-stale-blocked-by-in-a-BACKLOG-folder-is-outside-every-aperture | T | 50 | bug | progress.sh check's STALE-PARK aperture covers unfinished/, blocked/ and working/. It does NOT cover the per-lane backlogs, which is where open unclaimed work now lives -- so a blocked-by: naming a ticket that has since closed reads as GATED to every human and every agent, forever, and nothing reports it. Census 2026-09-06 at da2fea0fd: 18 distinct ranked non-umbrella tickets carry 20 such edges, across backlog-core (6), backlog-pascal (3), backlog-libs (3), backlog-nilpy (2), backlog-windows (2), backlog-tools (1) and unfinished/ (2). LIVE COST, measured not supposed: frankH found the first instance because frankA had told them an hour earlier that the exception-escape leak was blocked on decide-does-raise-of-an-existing-object-transfer-ownership -- which was in done/, settled for option (a) by the FPC oracle. The fix took an afternoon once the belief was removed. APERTURE NOTE THAT MUST SURVIVE ANY FIX: umbrella tickets legitimately name closed tickets -- membership is an EDGE and a closed member is PROGRESS, not staleness -- and 19 such edges exist. A check that reports those is a check people turn off. RE-CENSUS 2026-09-21 at 2f39aee23 (wider population, stated in the body -- carry both rows, do not read the difference as movement): the real signal is FLAT at 20 non-umbrella edges across 19 tickets, 16 of them fully unblocked and two at p55, while the umbrella noise grew 19 -> 44. That ratio is not an accident and will keep rising: an umbrella accumulates closed members BECAUSE the project progresses, so the false-positive floor of a naive check climbs monotonically with every ticket this fleet closes. The exclusion is load-bearing, not cosmetic. |
— |
| bug-t-a-ticket-citing-a-corpus-file-is-only-reproducible-by-whoever-has-that-corpus | T | 45 | bug | library_candidates/ is GITIGNORED and fetched per-checkout, and every checkout holds a DIFFERENT subset -- measured 2026-09-05 across ten: three have none at all, and of the seven that have some, no two match. Only frankA and frankZ hold fpc-testsuite. Meanwhile 19 tickets in OPEN folders cite a path under library_candidates/, and NONE of them says the input has to be fetched. The damage is not that a reader cannot reproduce -- it is that THE READER CANNOT TELL WHY. A missing corpus and an already-fixed bug produce the same silence, and a triager reaches for the second, because a ticket that will not reproduce looks stale rather than unrunnable. This composes with the two apertures already recorded (filed from the visible half, probed from the slug) into a third: PROBED ON A MACHINE WITHOUT THE INPUT. The remedy that works is frankS's: a hand-built reconstruction in the ticket body, which carries its own inputs and cannot be misread. |
— |
| bug-t-a-tier-job-identifier-is-a-selector-doing-double-duty-as-a-label | T | 55 | bug | TWO LAYERS, and the second is the more dangerous one. (1) A tier job's printed identifier is a SELECTOR doing double duty as a LABEL: job_selector() returns <target>#src:<srcs[0]> and its docstring says why — the first source is stable across renumbering — which is right for a --job argument and no reason at all for the string a human reads, titles a ticket after, or is handed as an assignment. Because every $(COMPILER)-dependent job inherits that target's prerequisites at the head of its source list, a multi-source job routinely names a file that is fine; three rows in 20260906T183724Z-6d04b14-seven.md do it and three sessions hit it independently in one evening. It is right about a THIRD of the time, which is worse than never, because the times it is right teach you to trust it. (2) The stored REASON is not the subject either: job_reason() returns the log TAIL (correctly — a signature list goes stale silently) and stub_reason() cuts it to 200 chars, so test-emit-obj's reason was THREE SUCCESS ECHOES, one of them a Makefile echo that prints only after the assertion it names has passed. It was read as the subject and propagated to three sessions. A reason is more dangerous than a name: a name is obviously an identifier, a truncated tail reads as a finished sentence about the subject while being a receipt for the last step that SUCCEEDED. THE EVIDENCE ALREADY EXISTS ONE FIELD AWAY — failed_step() and step_sources() record the failing recipe line and its own sources per red, with step_src deliberately "" rather than falling back — so layer 1 is not 'build a mechanism'. Layer 2 is a READING failure and explicitly NOT a proposed patch; the tail is the right return value. |
— |
| bug-t-armed-autopin-has-refused-62-consecutive-times-and-the-tree-has-had-no-pin-for-99-hours | T | 85 | bug | RE-MEASURED 2026-09-19 (frankH), AGAINST v411: auto-pin has NOT fired -- 16 shadow verdicts since v411 (all borg), 0 WOULD PIN; every pin since arming (v408-v411) was human-authorised. The OLD floor is gone: optdiff#shard0/12 and the three synapse rows PASS, and crtl_reachability.py still fails but is INHERITED from v411, so it no longer blocks. THE NEW FLOOR IS A DIFFERENT DEFECT OF THE SAME SHAPE: the first FIVE verdicts after v411 were consecutive and each had exactly ONE new red, lib-test#src:test/lib_mimic_xml_sax_xmlreader.npy -- which THE PIN ITSELF introduced. The row builds only with $(PXX_STABLE); v410 passes it 25/25 and v411 24/25 on identical live lib/, and the job last passed at 9b8475d4e, v411's own source tree, where it still ran under v410. A pin's inherited red set is recorded at a tree whose pin-built jobs ran under the PREVIOUS pin, so any pin-built job the new binary breaks is 'new' forever, and no HEAD fix reaches it without another human pin. Absent that red, the red criterion would have passed on two consecutive shas by 09-17 18:43 (the other pin_now conditions were not separately verified). Since 09-18 more reds joined (deadlock_diag, optdiff#shard11/12, test-esp-idf srchash). The 99 in the slug and the 64-verdict tables below are historical. | — |
| bug-t-borgs-enrolment-baseline-is-three-populations-and-only-one-is-the-tree | T | 45 | bug | borg's enrolment BASELINE:16 red at d4f170a4e7fb is at least three populations — 7 wasm32 RUNNER-ABSENT (fixed), 3+ blocked on missing 32-bit dev libs (needs root), and a residual that may be the tree. Undifferentiated, the whole 16 reads as a tree regression. | — |
| bug-t-check-has-no-aperture-for-a-ticket-slug-cited-in-source-and-195-of-them-resolve-to-nothing | T | 40 | bug | progress.sh check reads tickets and never reads SOURCE, so a ticket slug cited in a compiler or library comment is unchecked — and 195 distinct ones in compiler/** + lib/** resolve to no ticket. The common shape is not a missing ticket but a RE-WORDED one: pil.pas cites ...silently-turns-its-own-constructor-into-a-self-call where the filed slug is ...cannot-be-constructed-from-outside-that-unit, same bug, different words, so a reader who greps the cited slug finds nothing and concludes the ticket was never filed. The check's DANGLING-LINK aperture already does exactly this job for wiki-links inside ticket bodies; the gap is only which files it opens. Found because I landed a 196th in 3d3d90a2d and the check went green over it. |
— |
| bug-t-forwardlint-has-no-notion-of-nested-scope | T | 30 | bug | tools/forwardlint.py collects every procedure NAME in the include chain INCLUDING NESTED ones, with no notion of scope, and then flags any earlier TEXTUAL occurrence of that identifier -- call, variable, parameter or field alike. So a nested routine named generically is reported as an FPC-seed forward-reference violation that cannot exist, because a nested routine is not visible outside its parent. Measured 2026-09-22: a nested procedure Mark in dce.inc was reported against while PyPendLamCount > mark do in pyparser.inc, 38k lines earlier, where mark is a LOCAL VARIABLE and the line contains no call at all. The lint is otherwise doing real work and this is not a reason to weaken it -- the FPC-seed hazard it guards is real and has bitten twice in ir_codegen_wasm32.inc -- so the ask is scope awareness or a documented naming rule, NOT a looser match. Worked around at the call site by renaming the nested routines, which is why nothing is red today. |
— |
| bug-t-gate-sh-never-reaps-its-log-dir-and-an-exit-trap-would-break-the-tool | T | 45 | bug | tools/gate.sh:24 is LOGDIR=\"${TMPDIR:-/tmp}/pxx-gate-$$\" with mkdir and NO reaper and NO trap. Measured 2026-09-11: 78 directories, 5.3G, oldest 2026-09-10, still accruing -- the real consumer behind that day's /tmp pressure, which had been relayed to several sessions as a scratchpad problem. DO NOT FIX IT WITH trap ... EXIT: gate.sh writes its summary AND every per-step log into that directory on purpose (its own comment at :27) so a backgrounded run can be read from it, which is what CLAUDE.md tells every seat to do because the wrapper exit code lies. A trap reaps GREEN runs first, so it looks harmless until the first red nobody can diagnose. RETENTION IS UNCONSTRAINED BY CODE: no program anywhere reads a gate dir -- pxx-gate appears in exactly one .sh/.py/Makefile file, gate.sh itself, which only ever writes its own $$-named dir -- so a few hours covers it. Recommended: age-based reap at gate START (never at exit, so a run cannot delete its own output) plus kill -0 \"${dir##*-}\", since the dir name IS the pid, so a long concurrent run cannot be reaped by a threshold someone later tunes down. Rejected keep-on-red: it must read the verdict living INSIDE the directory it is judging, and getting that test backwards deletes exactly the reds. AND REAPING DOES NOT FIX THE ls -td /tmp/pxx-gate-* | head -1 HAZARD -- it makes it WORSE-behaved: a smaller population returns your own dir more often, so a method already filed as wrong (playbook:12418) gets certified more and corrected less. |
— |
| bug-t-lane-attribution-has-two-instruments-that-disagree | T | 45 | bug | 33 open tickets carry no track: frontmatter field, 30 of them in RANKED folders. The ranker still lanes them, via a cascade of fallbacks — a feature-track-t-* slug prefix, a Track X mention in the decl line, a Track bullet in the body. Any tool that reads fm.get('track') sees nothing for all 33. Two instruments, one answering about a field that is not there. Surfaced when a backlog sweep nearly mis-filed two tickets the ranker had been lanting as T all along; progress.py's own comment records the same class biting in the opposite direction on 2026-07-15. |
— |
| bug-t-no-automated-check-builds-lekkerzeilen-so-a-stated-goal-demo-can-break-silently | T | 55 | bug | lekkerzeilen is a stated goal (have lekkerzeilen compile under nilpy as demo) and NOTHING checks that it compiles — no tier, no gate row, no smoke target. THE WORKED EXAMPLE THIS WAS FILED ON WAS MY OWN ERROR and is withdrawn: I reported a compiler regression on 2026-09-20 and it was a relocated binary, not a defect (see rejected/bug-n-0c508e507-...). The gap stands on the ABSENCE, which needs no incident: at the time of filing, the only way anyone learns the demo stopped compiling is a human running it by hand. Cost is bounded and known — a clean compile is ~125 s and a resolution failure errors in ~19 s, so a check need not pay the full build to catch that class. NOT a request to add it to a tier: it lives in another repo and the tier rules forbid that, which is exactly what makes this a question rather than a task. Note the irony recorded honestly: the absence of a check is also why my own false alarm took four people-hours to settle — with a green demo row I would have known in one command that nothing was broken. |
— |
| bug-t-pasmith-returns-only-integer-kinds-so-optfuzz-is-blind-to-the-return-type-axis | T | 40 | bug | pasmith.py returns ONLY integer kinds from every function it generates -- measured across five seeds at optfuzz.sh's own flags, the complete set is longint/byte/word/longword/int64/smallint/shortint/qword. No float, no record, not even Boolean. It also declares no float variables at all, and while it DOES declare record types (3-4 per program) it never returns one. So tools/optfuzz.sh -- which exists specifically because curated gates missed 21 silent -O3 inliner divergences -- is structurally blind to ANY optimization keyed on return type, which is the whole admission axis of feature-opt-inline-float-and-record-returning-leaves. A clean optfuzz run on such a change is a guard that cannot fail, and it prints the same PASS as a real one. | — |
| bug-t-pinstatus-names-a-rollback-target-nobody-validated | T | 50 | bug | trackt pinstatus names v354 as the recovery target and pin_is_green() selects it, and v354 cannot compile a SINGLE ONE of the current tree's 54 lib/rtl root units. The selection asks whether a pin was green WHEN IT WAS TAKEN -- a true statement about the past -- and prints it where a reader needs to know whether it would work NOW. Nobody validated the target against the current tree, and the tool has the canary's own logic to hand. Fix is small and independent of the design fork (decide-pair-the-pin-with-the-lib-rtl-it-is-coherent-with): either run the check and report the real number, or mark the line UNVALIDATED. A tool naming a target nobody validated is worse than a tool naming none, because a named target is acted on and an absent one prompts a question. |
— |
| bug-t-progress-check-conflates-corruption-with-advice-so-nothing-can-run-it | T | 40 | bug | tools/progress.sh check already EXITS 1 on findings, so it is shaped like a gate -- and it is wired into nothing (no hit in tools/gate.sh, tools/sync.sh or .claude/hooks/). The reason is severity: it emits real CORRUPTION (DUP-SLUG, one ticket present in two folders) through the same channel and the same exit code as pure advice (STALE-PARK, NEAR-DUP, DANGLING-LINK, PROSE-EDGE-NOT-IN-FRONTMATTER, DEAD-COMMIT), and offers no way to select a subset -- check takes only --track, --strict and --write. Measured 2026-09-07 at 035a42c74: rc=1 with 24 findings, ALL advisory, ZERO hard. So wiring it into gate.sh quick today would redden every gate in the fleet permanently, which is the cry-wolf failure the owner has already ruled on; and because it cannot be wired in, the one class that is genuine corruption has no automatic detector at all. The fix is a severity split, not a new check. |
— |
| bug-t-publishing-a-claim-requires-moving-the-tree-so-a-session-that-is-measuring-cannot-claim-anything | T | 35 | bug | working/ is not in RANKED_STATUSES, so moving the folder is the ONLY act that removes a ticket from ready/next — owner: does not affect ranking and its body-bullet spelling collapses into the same field. Publishing that move is a commit and a push, and a push is a pull --rebase, which is the one act forbidden while your tree is the instrument for a running measurement. So the longer a session measures, the longer it holds an unpublishable claim, and every other session's tools CORRECTLY report the ticket free. _warn_claim_is_local already diagnoses this and names the legitimate hold; it offers no channel, so the claim's only remaining home is a peer message and whichever seat happens to be awake. |
— |
| bug-t-ready-and-next-show-only-the-slug-so-a-corrected-title-is-invisible-at-the-point-of-choice | T | 45 | bug | ready and next print the SLUG and nothing else descriptive -- no title:, no summary:. 259 of 598 tickets carry a title:, and progress.py reads it only in near/dupes (_head(), line 3021) for dedup scoring; it never reaches the queue output. That matters because THE SLUG IS THE CITATION KEY -- [[slug]] links and resolve <slug> both key on it and THERE IS NO RENAME SUBCOMMAND -- so the one field every chooser reads is the one field the TOOLING gives no way to correct, and the manual cost of correcting it SCALES WITH HOW LONG THE TICKET HAS BEEN USEFUL. Measured: of 6138 ticket slugs, 2432 are cited at least once; median 2 citing files, but 249 slugs are cited by >=5 and 34 by >=10, topping out at 37 (feature-demo-songformatter-pxx-target) -- and the heavily-cited ones are the long-lived campaign rows, feature-pascal-corpus-expansion at 32 being the current head of ready --track P. So the tool fails hardest on exactly the tickets that matter most. Fix: print title: under the slug in ready and next when present, falling back to the first clause of summary:. Cheap, no schema change, and it makes a rename unnecessary rather than merely affordable. |
— |
| bug-t-run-target-sh-s-exit-code-is-discarded-at-1082-call-sites | T | 65 | bug | Every Makefile row that runs a cross-target binary spells it \"$$(tools/run_target.sh <arch> $BIN)\", and a command substitution keeps stdout and throws the exit code away. Measured: 1082 of 1200 run_target.sh call sites are inside $$( ); 118 are bare. So a runner that cannot run is indistinguishable from a target that emitted nothing, and the comparison cannot fail for the right reason. This produced SEVEN auto-filed regressions on seven on 2026-09-04, all accusing the compiler, all caused by wasmtime not being installed. franka-29's RUNNER-ABSENT marker makes the message honest and leaves the mechanism intact. |
— |
| bug-t-six-real-program-jobs-are-in-no-tier-so-they-never-run | T | 65 | bug | SIX real-program jobs are in NO TIER: test-duktape, test-quickjs, test-chess-perft, test-sqlite-parity, test-fpc, test-wasm32. Their Makefile targets exist, their corpora ARE installed on seven (duktape, quickjs, chess, sqlite, plus fpc-rtl and fpc-testsuite all present under library_candidates/), and no tier ever invokes them -- so they have produced ZERO verdict rows in any host archive, ever, and can rot indefinitely without anything going red. This is the THIRD and FOURTH instance of a hole testmgr.py documents twice in its own comments directly above the TIERS dict: test-nilpy was in no tier until 2026-08-01, hiding 238 of 309 .npy files, and test-uforth until 2026-08-08, when grep -c uforth tools/testmgr.py answered 0. Both notes are still there, three lines above six more of the same thing. TIER SIZES: quick 1, native 6, limited 19, full 40, slow 1, opt 1. |
— |
| bug-t-stale-park-is-dominated-by-wall-ladder-tickets-that-can-never-stop-firing | T | 35 | bug | progress.sh check's STALE-PARK query is dominated by a ticket shape it can never clear: a WALL-LADDER whose body is a chronology of cleared blockers, so every rung adds prose naming a now-resolved ticket beside a blocking phrase and the hit count only GROWS as the ticket succeeds. Measured 2026-09-06 (frankD): feature-pascal-corpus-expansion alone produced 20 windows, and with feature-pascal-corpus-generics it was the ENTIRE STALE-PARK output for Track P -- so any real stale park in the lane sits behind two tickets that cannot stop firing. The existing escape, PARK CONDITION SUPERSEDED, excuses THAT BLOCK ONLY by design, which is right for an ordinary park and means a ladder needs one marker per rung forever. A check whose output is dominated by two permanent entries is as empty as one that never fires. |
— |
| bug-t-stale-park-is-the-one-prose-check-with-no-by-design-escape-so-an-adjudication-cannot-be-recorded | T | 45 | bug | MEASURED 2026-09-06 in tools/progress.py. Three of the four prose/citation checks carry a by-design escape a reader can write into the body -- DANGLING LINKS BY DESIGN (1868), PROSE EDGES BY DESIGN (2135), DANGLING SHAS BY DESIGN (2621, and the emit path TELLS you to add it, twice). STALE-PARK has NONE: greps for STALE PARK BY DESIGN and PARK BY DESIGN both return 0. PARK-CONDITION-REWRITTEN is a REFINEMENT of the hit, not an escape from it. So an adjudicated park re-reports forever, and the cost falls on exactly the tickets that are written well: prose that cites landed work BY NAME near a phrase like blocked is what a good write-up looks like, so the better the park, the more reliably it re-fires. Live case, and it cuts BOTH ways: feature-dynamic-compiler-tables was adjudicated in its own body at line 341 on 2026-08-30 and re-reported on its SECOND run -- but on re-reading, the slug that note dismissed as a pointer, not a blocker (feature-opt-dynarray-grows-in-place) has LANDED since, as has feature-emission-size-dce, so the second firing was a TRUE hit and the note was stale in the direction that mattered. The check was wrong about BLOCKING and right about STALENESS, on this report's own worked example. DO NOT copy the other three escapes verbatim -- a park's resume condition genuinely CAN become met later, so a blanket suppression would hide a true hit permanently. The escape must record WHICH resolved slugs were adjudicated, so a newly-resolved citation still fires. |
— |
| bug-t-sync-sh-pulls-under-a-running-sweep-and-a-ticket-push-is-the-case-people-walk-into | T | 45 | bug | tools/sync.sh does pull --rebase unconditionally, so running it during a sweep in the SAME checkout swaps test sources under a harness that reads them from the tree. Twice on 2026-09-06 by two seats who could both quote the rule: frankD lost a 2276-file census (two syncs, 4 files changed mid-sweep) and frankB lost a full suite over three Group 19 fixes (three ticket syncs; the tree picked up another seat's new test row against a binary built before it, and the RED landed on a file frankB had just edited so it read as theirs and had a plausible mechanism). THE HOLE IS EXACTLY THE SIZE OF THE WORK THAT FEELS SAFE -- nobody lands a code fix mid-sweep, and pushing a TICKET feels like paperwork; sync.sh does not know the difference because the pull is the same pull. A GUARD WAS WRITTEN AND NOT SHIPPED: argv-anchored, cwd-scoped to this checkout, refusing with a PXX_SYNC_DURING_SWEEP=1 escape. Its POSITIVE control fired correctly and its NEGATIVE control ALSO fired, so it was reverted rather than tuned -- sync.sh is in every lane's inner loop and a false refusal there is worse than the defect. THE DETECTOR IS NOW SOLVED (frankB): read a field the waiter cannot write into. argv is writable by every waiter and comm is not, so pgrep -x make matches the executable NAME and a shell running it cannot match its own probe -- a negative control that holds structurally rather than by luck. The bracket trick does NOT work (pgrep -f '[m]ake test' returns the identical pid set) because pgrep never puts its pattern in the matched processes' argv; that trick defends the tool against itself and does nothing about third parties. Measured: 3 orphan waiter shells match pgrep -f, aged 28.7h / 853s / 0s, the last being the measuring shell itself. Remaining work is the guard around that detector plus its devtest. |
— |
| bug-t-sync-sh-retries-a-push-whose-rebase-never-ran-and-calls-it-a-race | T | 40 | bug | push_with_retry() ignores rebase_onto_origin()'s result, and rebase_onto_origin() cannot detect a rebase that REFUSED TO START. git rebase aborting on untracked working tree files would be overwritten leaves ZERO unmerged paths, so the conflict loop's [ -z \"$conflicted\" ] && break treats it as resolved and returns. The push then fails non-fast-forward every time, and every iteration reports push raced another writer -- a diagnosis that is wrong, for a condition no number of retries can clear. Measured 2026-09-05: 42 attempts burned (12 then 30 via SYNC_PUSH_TRIES), all failing identically; the real error was only visible on a manual git pull --rebase, and the push succeeded on attempt 1 once the blocker was moved. THE FAILURE MESSAGE'S OWN ADVICE MAKES IT WORSE -- it says raise SYNC_PUSH_TRIES, which is exactly the wrong move here and is what I did. NOT the already-fixed exit-status bug (frankD, 2026-08-29): sync.sh correctly exits 1 and says YOUR WORK IS NOT ON ORIGIN. This is about the CAUSE it names, not about whether it reports failure. |
— |
| bug-t-test-core-reports-only-its-first-red-so-a-tier-with-three-failures-reads-as-one | T | 40 | bug | make stops at the first failing recipe, so test-core reports ONE red however many it has, and reports whichever is EARLIEST IN THE FILE rather than newest, worst or yours. Measured 2026-09-05: two independent reds — an AST slot-write census entry and a leak row that could never pass — took three full runs to enumerate, and after the first fix the second presented as though the fix had caused it. tools/gate.sh quick has the opposite shape: it runs all sixteen rows, prints a verdict per row, and finishes in ~30s. The CHEAP tier reports completely and the expensive one reports one line, which is backwards for the tier that exists to give breadth. | — |
| bug-t-the-aarch64-srchash-job-has-been-red-since-d36af549ea5b-with-no-ticket | T | 45 | bug | test-aarch64#src:tools/compiler_srchash.sh is one of TSTATE.md's four open regressions and is the only one with NO ticket, so nothing can dispatch a seat to it. Red on borg since d36af549ea5b (last good 481fb6d72ba0, 1 commit in range: b965f8633 'feat(A): settle the aarch64 C-ABI gate by measurement'), and still red at pin v418's own binary. The visible failure tail mentions 'no working llvm-objdump (tried llvm-objdump-21, ...)', which would make this a missing HOST dependency rather than a compiler defect -- and that IS now established (frankb-8e, 2026-09-22): it is the missing host dependency, and the 'verified' fixedpoint line beside it is the PASSING half that the truncation joined to it. Three measurements, not a reading of the tail: tools/aarch64_cabi_prologue_probe.sh exits 2 on a missing objdump BY DESIGN and prints exactly that text, saying in its own words that it is an INSTRUMENT failure and not a statement about pxx; b965f8633 changed ZERO files under compiler/ or lib/ so it cannot have moved a fixedpoint; and with llvm-objdump-21 present the probe PASSES -- exit 0, 5 signatures agreeing with clang, 2 stack-passed skips. THAT DISCHARGES THIS TICKET'S OWN RE-LANE CONDITION IN THE NEGATIVE (it says re-lane to A if the probe fails WITH llvm-objdump present; it passes), so it STAYS T. THE EXIT 2 IS NOT THE BUG AND MUST NOT BE MADE A SKIP -- the script records that exit 0 was considered and rejected as laundering, citing done/bug-t-tstate-launders-skip-into-pass, and an earlier version of this gate that only checked the name was non-empty printed a FABRICATED 'DISAGREEMENT -- 5 pxx-side broken'. RESIDUAL, with an owner: borg needs llvm-objdump installed (a host change), or the harness should report exit 2 as INSTRUMENT-UNAVAILABLE distinctly from a finding at exit 1 -- a T harness change, the better fix because it generalises, and the script already separates the codes while nothing downstream reads the distinction. Filed as T because a missing toolchain binary is T's; RE-LANE TO A the moment someone shows the probe fails with llvm-objdump present. | — |
| bug-t-the-auto-filed-fallback-lane-routes-every-regression-to-the-one-lane-that-may-not-own-it | T | 50 | bug | twatch auto-files a regression with track: T as a documented FALLBACK and tells the reader to re-lane it before working. Measured 2026-09-09 at 9945b2514: 12 of the 20 open regression tickets still carry it, aged 2026-09-02 through today, all at prio 70 — and git log over those files shows ZERO re-lanes ever, only the watcher's own tstate-ticket(seven) updates. Ran the queue rather than reasoning about it: 7 of the top 9 rows of ready --track T are these, above every real T tool ticket except two at prio 80; and the newest of them appears in neither ready --track A nor --track P. So the fallback puts a bug in front of the one lane whose own rule is owns the TOOL, never the BUG, and hides it from the lanes that could own it. NOT the same defect as bug-t-lane-attribution-has-two-instruments-that-disagree, which is about tickets with NO track field; here the field is present, deliberate and wrong. |
— |
| bug-t-the-bench-tier-published-red-twice-with-zero-bench-rows-and-no-report | T | 60 | bug | bug(T): the bench tier has published RED with ZERO bench rows on EVERY run since 2026-09-11 -- the fleet has no bench data at all | — |
| bug-t-the-c-conformance-corpus-is-absent-from-this-checkout-so-make-test-c-covers-less-than-its-name | T | 25 | bug | In the frankA and frankB checkouts library_candidates/c-testsuite/tests/single-exec does not exist, so make test-c-conformance and all four cross rows SKIP and make test-c delivers only its test-core half. THE CORPUS IS GITIGNORED, SO ITS PRESENCE IS PER-CHECKOUT AND NOT A PROPERTY OF THE BOX OR THE HARNESS -- it is present in 6 of 24 checkouts under /home/neo including trackt-watch, and seven publishes all 30 test-c-conformance jobs PASS at its most recent full tier (0975f200bd17, 2026-09-03 12:57Z), which is stronger evidence than any directory listing because a job cannot pass without its corpus. NOTHING HAS BEEN UNCOVERED FOR ANY PERIOD; this ticket was first filed claiming exactly that and the claim was false. What is real and left: a Track C worker in one of those checkouts who runs the documented gate gets less than it says. Fix is tools/install_lib_candidates.sh c-testsuite in the affected checkout, a network fetch, hence not an agent's call. The MISLEADING part is already fixed in 72c431bd9: test-c-conformance-cross printed all targets green over four skips and test-c printed c-conformance green, and both now branch on the suite directory and say NOTHING MEASURED / SKIPPED. |
— |
| bug-t-the-conformance-runner-lets-a-caller-read-around-its-own-directive-extractor | T | 45 | bug | run_pascal_conformance.sh has a directives() extractor that has been right every time, and nothing makes a caller go through it. In one session three different misreadings each came within one step of converting a defect into a green row: a lower-case { %fail } a grep missed, --retry-skips reporting exit-clean as PASS when the harness never compares output, and fpc built no binary read as unit-shaped when fpc was REJECTING the program. Three disguises, one cause. Proposal: a row cannot be unskipped without the extractor and an fpc OUTPUT diff. |
— |
| bug-t-the-conformance-runner-reports-an-empty-corpus-as-a-normal-green | T | 45 | bug | tools/run_pascal_conformance.sh guards a MISSING suite directory (prints SKIP) but not a PRESENT-BUT-EMPTY one: that prints 0 pass, 0 fail, 0 skip, 0 auto-gated (of 0) and exits 0 — a line shaped exactly like a result, with (of 0) the only tell. Both cases exit 0, so a caller reading rc cannot separate no-corpus, empty-corpus and green. Measured on this box. 22 of 28 checkouts pass this target by absence, and the group that bit had library_candidates/ present with the suite under it missing, so a presence check on the parent passes and the corpus still is not there. |
— |
| bug-t-the-crtl-census-writes-fixed-names-into-a-caller-supplied-scratch-dir | T | 40 | bug | crtl_declaration_census.sh writes FIXED names ($TMP/crtl_census.c, .log and the binary) into a caller-supplied tmpdir, and its verdict is grep -q 'does not define' $LOG. Two concurrent invocations sharing TESTTMP have one grepping the other's log and one exec'ing a binary the other is rewriting. NOT the cause of the 2026-09-17 lib-test#55 red -- that was the pinned compiler and is fully explained (red under pin v410, green at HEAD, cleared by pin v411) -- so this is filed as a live hazard on its own evidence, not as a diagnosis of that failure. |
— |
| bug-t-the-documented-build-path-never-enumerates-what-it-needs | T | 45→80 | bug | FROM AN ATTEMPT, not the backlog. Build-from-clean now WORKS in a container with only git and make -- verified 2026-09-06 in podman/alpine (musl, no bash, no fpc, no gcc): seed from the committed pin, make compiler/pascal26, converged in 1 round to the same sha as the host. But git and make were installed BY THE TESTER via apk, so the one step still unmeasured is a box that lacks them, and NOTHING in the repo states the requirement. The prerequisite set is currently folk knowledge: this attempt found bash was assumed and absent (fixed, 79264f396), which is exactly the shape of an unstated dependency -- it did not error usefully, it silently disabled a guard. Wants a stated, TESTED prerequisite list, not a README paragraph nobody runs. |
— |
| bug-t-the-five-gtk-regressions-are-one-missing-host-dependency | T | 55 | bug | Seven lost its GTK development headers to the 2026-09-05 dist-upgrade (removed 15:20-17:30, reinstalled by hand 17:59:31), so the 09-05 batch of five gtk jobs failed there, auto-filed, and was closed by whoever verified on a host that has them. CORRECTED 2026-09-06: the 'it has happened four times' recurrence argument is FALSE and the other three batches are NOT this condition -- 08-21 ran on plexus and its own log tail shows gtk_init SUCCEEDING; 08-30 and 09-01 both failed deep inside headers that were present, against two different code defects, each root-caused and fixed. The five test NAMES recur because they carry the widest header surface in the suite, not because one condition recurs. The durable fix stands and is strengthened: a job that cannot tell 'the feature is broken' from 'the toolchain is absent' -- and a ticket set that cannot tell four causes apart -- produces closures nobody can audit. | — |
| bug-t-the-full-matrix-switches-itself-off-when-the-fleet-is-busy | T | 60 | bug | — | |
| bug-t-the-full-suite-hook-refuses-writing-about-the-suite-not-just-running-it | T | 35 | bug | .claude/hooks/no-full-suite.sh matches the COMMAND TEXT, so it refuses commands that merely CONTAIN a suite name in prose rather than invoking one. Three refusals in one session, none of them a suite run: a heredoc writing a ticket whose body said gate.sh full, a logbook line naming a test/ glob, and a git commit -F - whose MESSAGE said make test while explaining why the quick tier was enough. Each cost a retry through a different tool. The guard is right and must stay; it is the aperture that is wrong — it cannot tell make test from a commit message about make test. |
decide-t-the-full-suite-hook-refuses-prose-about-the-suite |
| bug-t-the-full-suite-hook-scans-heredoc-prose-and-refuses-documentation | T | 35 | bug | The shell-loop rule in .claude/hooks/no-full-suite.sh scans the whole command text, so a HEREDOC BODY is judged as if it were a command: documentation mentioning a test glob and containing the word for — e.g. quoting a Pascal for i := 0 to n loop — is refused as 'a shell loop over a test/ glob'. No loop, no glob expansion, no suite. CORRECTED 2026-09-09: THE TWO-LINE REPRO BELOW DOES NOT REPRODUCE when spelled as this row instructs, and it did not on the day it was filed — the hook is byte-identical to 448b21c11 (2026-09-03). The rule is never reached, because the read-only first word cat exempts the whole command first. What actually decides it is a SEMICOLON anywhere in the prose being written: it blanks that exemption via the chain rule, which tests the whole string including the heredoc body. Add one to the repro's first sentence and it is refused. The defect is real and the mechanism named here is the wrong one. |
decide-t-the-full-suite-hook-refuses-prose-about-the-suite |
| bug-t-the-full-suite-hooks-commit-message-exemption-does-not-cover-how-anyone-writes-one | T | 50 | bug | MEASURED 2026-09-04. .claude/hooks/no-full-suite.sh deliberately exempts a commit message that QUOTES a forbidden command -- its own comment says an un-exempted one was silently deleting the message span -- but the exemption is keyed on the command's FIRST WORD being git, and git is the only first word that survives a chain. Nobody writes a multi-paragraph commit message as git commit -m: the repo's own practice is to write it to a file with a quoted heredoc (CLAUDE.md requires the quoting) and then git commit -F. That command's first word is cat, or cd, and the heredoc BODY is what the hook scans. So the exempted shape is the one nobody uses and the used shape is unexempted. Cost here: a commit refused because its message explained that a census used to run only in the test-core target. The refusal is visible and reword-able; the failure mode the hook's own comment names -- a silently truncated message -- is not. |
decide-t-the-full-suite-hook-refuses-prose-about-the-suite |
| bug-t-the-job-map-cannot-be-asked-whether-a-given-source-was-exercised | T | 65 | bug | A tstate job is named after its group's FIRST source, so every later source in the group is invisible by name while being fully covered. Measured at 5b5fdb0b32d3: 384 of 3264 test/ sources (~11.8%) have no job key of their own, so for one source in eight grep the job map answers a DIFFERENT QUESTION and returns nothing. Hit live while checking whether test_record_class_var_fail had run — it had, as the 4th compile line of test-core#src:test/strict_fpc_case_fail.pas. This is the QUERY direction of bug-t-a-job-named-after-its-first-source-file-cannot-name-its-failing-step (done/), which covers the job's inability to name its failing STEP and not a reader's inability to ask about a source. RAISED 50->65 on 2026-09-05: two MEASURED wrong readings during one night of live tier triage, both with attributable cost — one key standing for six unrelated targets (sqlite-threads x4, uforth, emit-obj) so the tier's red DENOMINATOR was unknown until settled by hand, and one job's history SPLIT ACROSS TWO KEYS when its recipe changed, which made test-uforth look like it had never run and pointed at a ~6.5 week bisect window instead of the true 234 commits. The key is derived from the recipe's TEXT rather than from the job's subject, so it is both too coarse and too brittle. 2026-09-06 adds the third and worst failure mode, SILENT REPOINTING, and the same day CORRECTED ITS OWN MECHANISM -- see the two dated sections, the first of which is wrong and kept. @N indexes JOBS that share a first source, NOT occurrences of that source in the Makefile: three test-xtensa jobs begin with test/test_cross_record.pas (#84 3 lines, #138 6 lines, #147 115 lines), so @3 is #147. That is STRICTLY WORSE than the version first filed here, because a Makefile occurrence can at least be counted by reading the file, while the job list is produced by the harness's own recipe grouping and is invisible in the source it indexes -- you cannot resolve the key without asking testmgr. Add a job that shares the first source earlier in the target and @3 still resolves, still names a real row, and now names a different one: the verdict history stays attached to a key whose SUBJECT changed underneath it, with no error, no gap and no split to notice. MEASURED COST, this file's own author: I read @3 as a Makefile occurrence, measured #138, and published a 'not reproducible' exculpation for #147, which was genuinely red. The subject is (target, abi, source) and all three are already in the recipe. |
— |
| bug-t-the-pascal-i386-relocation-row-asserts-a-count-with-no-precondition-and-passes-on-nothing | T | 55 | bug | The Pascal i386 absolute-relocation row in test-emit-obj asserts R_386_32 count == 0 over readelf -rW output with NO precondition that anything was read, so it passes on an empty relocation section, on a missing file, and — measured — on the string not an elf file. awk ... END{exit (n+0)==0 ? 0 : 1} leaves n unset when nothing matches, and unset is zero, which is the expected value. The C-side row TWENTY LINES ABOVE IT already carries the missing guard — it asserts the RELATION pcr > abs before asserting abs == 0, and its comment says exactly why: 'A bare nonzero PC32 count stays green after a whole family stops converting; this does not.' The reasoning was written down and not carried down. Second, independent weakness reported by frankD from the row they cleared: the count can reach 0 by ACCIDENT — one extra unrelated local in PXXIoCheck moves code off -0x10 and the count goes 1 -> 0 with the conversion untouched — so even a correctly-read zero does not prove the mechanism. Fix is the pattern already in the file: assert the population is non-empty and assert a relation, not a bare count. |
— |
| bug-t-the-shell-loop-rule-reads-prose-as-a-loop-and-teaches-the-reflex-that-defeats-it | T | 40 | bug | no-full-suite.sh rule 3 fires when a command contains BOTH a test/*.pas-shaped string and the bare word for — and both conditions are met by a python heredoc iterating in memory, and by a heredoc WRITING A TICKET whose prose happens to say test/*.pas and for. Hit twice in one session while doing neither. The same file already recognises this class and fixed it for rule 2c ('reading about the rule, not running it, and refusing that is pure noise — the first thing this rule did on the day it landed'); rule 3 did not get the treatment. The cost is not the retry: the documented escape is PXX_ALLOW_FULL_SUITE=1, so the lesson a agent learns is to prefix it reflexively, which is exactly how a guardrail the owner asked for twice stops guarding. |
decide-t-the-full-suite-hook-refuses-prose-about-the-suite |
| bug-t-the-sort-comm-locale-desync-has-now-been-found-three-times-independently | T | 40 | bug | Under a UTF-8 locale sort ignores punctuation at the primary level while comm compares bytes, so a name containing -, _ or / sorts into a position comm does not expect. comm prints file 1 is not in sorted order to STDERR and KEEPS MERGING out of step, so the caller gets a wrong answer and a zero exit. Three tools here hit it INDEPENDENTLY and each fixed it in place with its own explanatory comment: elf_alloc_same.sh, selfhost_stamp_devtest.sh, and busybox_diff.sh (44e7ea61f, today). All three are correct NOW. This ticket is that there is no shared helper and no lint, so the fourth caller will write the bug again — two is a smell, three is a design flaw. |
— |
| bug-t-the-tmp-sweep-guards-against-reaping-its-own-scratch-and-not-a-foreign-live-runs | T | 45 | bug | — | |
| bug-t-thirteen-devtest-guards-assert-a-code-line-s-spelling-as-a-proxy-for-a-behaviour | T | 45 | bug | Census of all 138 tools-devtest files, prompted by 9bd00df46 where a guard asserted the literal \"$bin\" and read a rename as a deletion. 82 read repo source text; 17 make 21 literal assertions against it; 13 of those assert a CODE-SHAPED literal — an exact assignment, dict entry or comprehension — as a proxy for a behaviour that is observable elsewhere. Every one goes RED on a reflow that changes nothing and GREEN on dead code, which is both failure directions at once. The one found so far was found by accident, because it happened to go red; the rest are in the state it was in BEFORE the rename. Clean on one axis and it is worth recording: all 9 split-anchors fail loud (IndexError), none silently returns the whole file. |
— |
| bug-t-three-compiler-spellings-opt-out-of-the-testmgr-snapshot-silently | T | 40 | bug | 6aa50d6eb fixed COMPILER_PATH_RE's matches-TOO-MUCH mode (../../compiler/pascal26 had its .. prefix survive the rewrite). The matches-TOO-LITTLE mode is untouched and was never filed. Measured across all recipe rows naming the compiler: ./$(COMPILER) 5717 rows rewritten correctly, ../../$(COMPILER) 1 row now fixed, and THREE spellings still skipped -- $(CURDIR)/$(COMPILER) (Makefile:4119, test-nilpy), \"$root/$(COMPILER)\" (Makefile:28806, test-uforth), and a bare compiler/pascal26 (Makefile:155) passed as an argument to a script. The first two SILENTLY RUN THE WORKTREE BINARY instead of the per-run snapshot. Latent rather than standing: the snapshot is a copy2 of that same binary, so they diverge only when a rebuild lands mid-run -- which is exactly the scenario the snapshot exists for, and testmgr already reports compiler_changed_mid_run because it happens. |
— |
| bug-t-tools-devtest-is-a-growing-sequential-sweep-behind-one-budget | T | 60 | bug | tools-devtest#00 runs every tools/*devtest*.py script one after another in a single job, so its wall time is the SUM of a set everyone is encouraged to add to, behind a CONSTANT budget. That mechanism is the ticket and it is untouched. THE GROWTH-RATE ARGUMENT THIS SUMMARY USED TO LEAD WITH IS RETIRED: it read 207s/~130 (09-01) and 354.5s/149 (09-06) as 1.71x in five days, and the 09-17 section below already called that a slope drawn partly through a bug (host_dev_lib_skip_devtest.py held a quadratic worth 17 minutes on its own). Re-measured 2026-09-22 on the same box and condition -- 344.0s over 166 scripts, plexus quiet, tree 0e864874e, frozen-tree guard green -- the sweep GAINED 17 scripts and LOST ~11s of wall. THE DECIDING MEASUREMENT THIS TICKET ASKED FOR NOW EXISTS AND IT REFUSES BOTH SPLIT RULES PROPOSED BELOW, WHICH ARE BOTH COUNT RULES: the distribution is extremely skewed -- median 0.22s against a 2.07s mean (9.4x), 131 of 166 scripts under a second summing to 10% of the wall, while the top 8 are 70.2% and the top 1 (fpc_trunk_verdict_devtest.py, 78.5s) is 22.8%. Simulated over the real timings at N=8, a count split spans 11.4s..110.6s and admits no budget that is meaningful for both ends; weight-aware greedy gives 78.5s, and THAT SHARD IS THE ONE HEAVY SCRIPT ALONE. So shard by measured WEIGHT at N=4 (344s -> 86s, 4.0x); past N=6 the heaviest single member binds and wider sharding changes nothing, which makes the next lever that script rather than a bigger N. A weight split needs deterministic assignment or a still_red keyed on shard name stops meaning anything, so the assignment comes from a CHECKED-IN script->weight table -- which is itself a summary of last-known timings and decays silently, mis-balancing shards without reddening anything, i.e. the same object as the stale summary this ticket just had repaired. It therefore carries a refresh design rather than a promise: a devtest reds when any script in the glob lacks a row or any row names a deleted script (so decay is impossible rather than detectable, at devtest speed); each shard prints predicted-vs-actual and reds beyond ~2x (so every tier run re-measures the table for free, and a script growing toward a pathology is visible WHILE it grows); and an absent entry defaults HIGH (p90), because the choice turns on which way the error breaks rather than on accuracy -- under-estimating blows a shard budget and reds something that is not a defect, over-estimating only wastes part of a scheduler slot. THE BUDGET QUESTION IS UNTOUCHED AND THIS MEASUREMENT DOES NOT LICENSE A CHANGE TO IT IN EITHER DIRECTION: it is a quiet-box reading with no tier contention, the job has still never completed inside a full tier (n:0), and 600.1s remains a CENSORED lower bound. The single job also still reports one verdict for 166 scripts, with a stored reason that is a fixed-width tail naming passing progress lines rather than the failure. |
— |
| bug-t-would_pin-false-reads-as-a-refusal-and-must-say-what-it-is | T | 55 | bug | ROUTED BY THE OWNER 2026-09-06, recommendation 2 of the p80 pin decision. pin_shadow() publishes would_pin: false and it has ZERO authorised consumers -- the function's own docstring says it 'deliberately never touches pinned, make pin, or stable_linux_amd64/**'. It is read as a refusal anyway: THREE sessions reasoned carefully from it and all three read permission where none was expressed, and the fleet cut no pin for 49 hours. The fix is the wording, not the reader -- CLAUDE.md says so about this exact field. Make the output state what it is and what it is not, e.g. 'advisory -- 12 reds this pin does not have; pinning is NOT blocked'. THE TEST FOR ANY REPLACEMENT: a session reading only that line, with no CLAUDE.md in context, must not be able to construe it as authority. A boolean named would_pin cannot pass that test whatever its docstring says, so renaming the FIELD is in scope, not only its rendering. |
— |
| feature-t-a-guard-whose-runtime-is-implausibly-small-for-its-population-is-sampling | T | 35 | feature | gate.sh's pinned builds live lib/rtl row printed PASS in 1s while sampling one fixture of 111 units, and the seam had moved off that fixture (b6212f43f). Four gates ran that afternoon and none measured the row; cost was the only tell and nobody read it. A guard's RUNTIME is a checkable proxy for its POPULATION — implausibly cheap for the work it claims means it is sampling — and unlike a positive control it needs no knowledge of the defect. Proposes recording per-arm wall time and flagging an arm whose cost falls far below its claimed denominator. NOT a timing benchmark and must not become one: the question is orders of magnitude, not milliseconds. |
— |
| feature-t-a-test-s-expected-transcript-should-live-beside-the-pas-not-in-the-makefile-recipe | T | 50 | feature | A whole-transcript test's expected output lives in an inline printf inside a 12000-line Makefile, so EXTENDING THE TEST LOOKS COMPLETE FROM INSIDE THE TEST -- you add rows to the .pas, the .pas is self-consistent and its own comments agree, and the assertion it is judged by is in a file you never opened. That is what cost 18 hours of RED on the native tier (2ba37ba91 added rows j..n; the printf still said a..i). Proposal: let a .expected file beside the .pas be the default source, as several tests already do, and keep the inline printf only where the transcript is target-dependent. NOT started -- filed at frankuser's suggestion and explicitly not to be done without asking, since it touches many recipes. |
— |
| feature-t-enrol-test-wasm32-in-a-tier-so-something-samples-the-backend | T | 45 | feature | test-wasm32 exists (a6d7bfc08, 2026-09-02) and appears in NO tier: grep -c wasm tools/testmgr.py is 0, and gate.sh names wasm only in two comments. So wasm32 is sampled by nothing -- not the inner loop and not the watcher -- while being a real backend with a target number, a runner arm and 22 of 27 measured-green rows. The cost is not tidiness: frankD's wasm32 defect is a name scan that stops matching silently, producing a module that compiles ok:, links, and traps at run time on a host function it never imported, at byte-identical size to a program that never touches a file. A silent-wrong-answer class behind a green, in the one backend nothing samples. frankwasm landed two import-asserting rows in test-quick (no wasmtime needed) and stopped at enrollment because that is a Track T cost judgement, not its call -- and because the hook denies make test*, so it could not verify test-wasm32 passes and declined to go around the guard. VERIFIED 2026-09-05 by frankD: test-wasm32 is GREEN -- 53 rows, exit 0, at 4ef367091 / binary 25113fd3. AND ENROLLING IT TODAY IS STILL WRONG, for a reason this ticket did not have: the harness CANNOT TELL A HOST GAP FROM A REGRESSION. run_target.sh signals a missing runtime well (distinct RUNNER-ABSENT text on both streams, exit 2), the Makefile rows discard that exit code through command substitution, expect_same.sh compares the RUNNER-ABSENT TEXT against expected output and reports MISMATCH exit 1, and grep -n RUNNER-ABSENT tools/testmgr.py returns NOTHING. So a missing runtime auto-files as a compiler regression -- not wasm32-specific, every qemu arm reaches runner_absent the same way. Enrolling in full today reproduces the six-regressions-in-one-run incident on seven BY CONSTRUCTION. The blocker is SKIP ACCOUNTING, a Track T design call, not wiring. RE-MEASURED 2026-09-06 at d11b8a1a9: still green, 53 rows, MAKE_EXIT=0. AND THE JOB IS BIGGER THAN THIS TICKET SAID: test/wasm/check_all.sh is a SECOND suite, 42 checks, invoked from NOWHERE outside test/wasm/ -- no Makefile target, no tier, no script -- so wiring test-wasm32 alone still leaves the larger of the two unwired. |
— |
| feature-t-freebsd-image-and-runner | T | 20→55 | feature | UNBLOCKED 2026-09-01 -- the permission it waited on was APPROVED 2026-08-31 (decide-install-qemu-system-and-a-freebsd-image-on-plexus) and this ticket was never moved out of blocked/. Owner restated it 2026-09-01: 'we are allowed to install a bsd image on qemu, i thought we already answered that. or maybe i only answered for openbsd, either way, same answer' -- so it covers OpenBSD too. Stays prio 20: permission granted is not priority raised, and BSD is demoted under the linux-only focus. ORIGINAL: Nothing on plexus can boot a FreeBSD kernel — qemu-system-x86_64 and qemu-img are not installed, /var/lib/libvirt/images does not exist, and no freebsd image is anywhere on the filesystem. That is the only thing standing between feature-port-freebsd-native and a start, and it is infrastructure, not compiler work, so it belongs to T. | — |
| feature-t-twatch-should-assert-its-repro-selector-resolves-to-the-one-job-it-is-filing | T | 55 | feature | feature(T): twatch should assert its ## Repro selector resolves to exactly the job it is filing |
— |
| feature-toolchain-cli-ux | T | 30 | feature | FIVE OF THE SIX FLAGS ARE LANDED AND THE SIXTH IS A DECISION, NOT AN IMPLEMENTATION. --version, --where/--config, --list-targets, --list-libraries and --doctor all answer with no source file and exit 0, are covered by test-quick rows so gate.sh quick sees them, and are built to be unable to drift: --where calls the SAME routines a real compile calls (ResolveToolchainDirs / AddDefaultPasUnitDirs / AddDefaultCIncludeDirs) rather than re-deriving the search rule, and --list-libraries SCANS the resolved directories through PxxListDir rather than reciting an inventory. Config tiers 1, 2 and 3 are all in: CLI flags, then PXX_HOME/PXX_LIBPATH (all-or-nothing, so a typo shows as [MISSING] instead of half-applying), then pxx.cfg, then ExeDir defaults. ONLY --selfcheck IS LEFT, it answers unknown option (re-measured 2026-09-05 at HEAD), AND IT IS BLOCKED ON INTENT rather than on work: feature-release-packaging specifies check 1 as pxx -> gen1, gen1 -> gen2, cmp gen1 gen2 -- a real fixedpoint STEP that requires RUNNING the freshly built binary -- while the compiler spawns no process (no PalVforkAndExec/PalFork anywhere under compiler/) and locates itself only through ExeDir. Every in-process substitute asserts something WEAKER under the same trusted name, and tools/selfcheck.sh already does the specified thing and already ships in the release tree, so do nothing is a live option. Filed as [[decide-what-should-pxx-selfcheck-assert-when-the-compiler-cannot-spawn]] with four options and a recommendation; that ticket is still status: new, owner: user. IF THE ANSWER IS USE THE SCRIPT, THIS TICKET CLOSES ON THE ANSWER ALONE. User-facing docs for the five landed flags are a Track D job and are not filed here. |
decide-what-should-pxx-selfcheck-assert-when-the-compiler-cannot-spawn |
| regression-cascade-154d1aa3fba6-has-no-ticket-and-its-range-cannot-explain-its-jobs | T | 55 | regression | The oldest open cascade on seven -- 18 jobs, bad 154d1aa3fba6, last good e417731e9007, open since 2026-08-29 -- is the only one of three with NO ticket, and its range cannot explain its jobs. Nine of the twelve commits in range touch buildable files and ALL NINE are the Rust frontend (compiler/rparser.inc + Makefile + Rust test rows); the 18 failing jobs are cross-target extern-C rows on i386/arm32/aarch64, an xtensa object row, sqlite-threads-aarch64, two lib rows, three NilPy rows and tools-devtest#00. rparser.inc is on none of their paths and the only shared file in the range is the Makefile. So either a Makefile hunk broke a shared recipe path, or the cascade is not attributable to the range at all. FIRST ACTION IS A RE-RUN, NOT A BISECT: one of the 18 jobs at HEAD settles it in one measurement. Lane is a FALLBACK -- if the cascade is real the defect is Track A's or B's, and nothing here says it is T's. |
— |
| task-t-a-makefile-recipe-that-is-not-valid-sh-passes-every-gate | T | 25 | task | Appending to a looped test-core recipe at an anchor INSIDE a for arch ... done continuation put a RED on origin for hours (ebc0dcb4f..ca6b96843: sh: 17: Syntax error: \")\" unexpected (expecting \"done\")), and five instruments were green because each is correct about something else -- --job src:<file> selects the recipe line for the file you NAME, make compiler/pascal26 does not read test-core, --tier quick does not run it, and gate.sh quick's Makefile-assertion row checks that assertions can FAIL, not that a recipe is valid sh. The obvious mechanism was ATTEMPTED and measured not to work: sh -n over every logical recipe line gives 190 hits, essentially all regex mangling of $(...) across continuations -- a ~100% hit rate, as empty as a check that never fires. So the hard part is the CONTINUATION JOIN, not the sh -n. Filed as the residual frankB deliberately did not land, so the next person to have the idea starts from the 190 rather than from zero. |
— |
| task-t-a-release-grade-full-green-needs-the-corpora-installed-skip-holes-is-forty | T | 60 | task | skip_holes == 0 IS ACHIEVED AND MEASURED: 0 skip-holes at 25c21aedc on plexus / qemu 10.2.1, full tier, 4952 PASS / 1 FAIL / 1 FLAKY / 0 SKIP of 4954, 1589.3s, frozen-tree guard green and aimed. QUOTE IT WITH THE HOST OR NOT AT ALL -- it is NOT "release-grade green", because the archive holds borg''s c_crtl_wait.c failing 550 of 551 on qemu 8.2.2 and a host-free sentence gets reconciled against that within a day. THE BLOCKER THAT MADE THIS A TICKET WAS AN UNMEASURED ADJECTIVE AND IT IS GONE: this said "not started because the volume is at 94%" while nobody had sized the fetch. Measured by replicating the tool''s own fetch_commit into a scratch dir (depth-1 + sparse, .git excluded from the copy, so upstream repo size is irrelevant): the whole thing is 43 MB -- fpc-testsuite 20,060 KB, sqlite 10,492, fpc-rtl 5,756, c-testsuite 2,988, synapse 2,092, lua 1,448, fcl-json 1,356, cjson 104. Predicted 44,296 KB before installing, actual delta 44,308 KB, twelve KB out. 0.4% of the 9.9 GB free, so there was never a cliff and never an owner decision; the ~185 MB transient lands in mktemp -d on the SEPARATE 94 GB /tmp volume by construction. INSTALLED 2026-09-22 (gitignored, nothing entered the repo); disk still 94% with 9.8G free, inodes still 13%. WHAT THE INSTALL BOUGHT, both denominators because quoting one alone is how this ticket already went wrong once: runnable jobs 4910 -> 4954 (+44, the honest measure) and total enumerated 4950 -> 4954 (+4, the one that hides the effect). THE RUN IS RED AND THE RED IS A FIRST OBSERVATION, PRE-REGISTERED AS SUCH BEFORE THE VERDICT: test-pascal-conformance#shard3/6 fails on terecs4.pp (%FAIL test compiled), and that shard was SKIP 0.0s in the previous run because it needs fpc-testsuite -- so the row had never executed on this box and the previous green is not a control for it. Measured at compiler 06255ab1878c: a destructor in a RECORD under {$mode delphi} compiles clean with no diagnostic AND NEVER FIRES (probe prints scope-in/scope-out, body unreached), so we do not support record destructors, we only fail to reject them. Tagged gap: accepts-invalid per terecs1.pp''s precedent since the goal doc decides the class -- us accepting what FPC rejects is not a defect, a differing diagnostic is deferred -- and shard 3 then re-runs 69 pass / 0 fail. It is the WORSE sub-shape of that class and the tag says so: siblings leave the mistake visible at the use site, here there is no use site to fail. THE FLAKY QUALIFIES THIS FAMILY''s OWN HEADLINE: c_crtl_wait.c flaked on 10.2.1 (failed attempt 1, passed attempt 2) -- the census''s 0 RED / 361 counts REPORTS and a report is green if any of three attempts passes, so per-report 0/361 stands while per-ATTEMPT 10.2.1 is NOT zero. "Clean on 10.2.1" is the wrong phrase and I used it; the accurate one is deterministic-fail on 8.2.2, retry-absorbed on 10.2.1, which fits the same night''s load finding and makes the upgrade a certain-red-to-rare-flake trade rather than a green. ALSO FIXED HERE, because two seats subtracted a pre-run banner from an end-of-run total and invented a cause: the CORPUS MISSING -- N job(s) banner is computed before the first job starts and cannot see an in-run corpus skip, so its 40 against the report''s 46 SKIP looked like six skips with another cause and was one cause counted twice (the six were the NATIVE test-c-conformance shards; the banner''s 24 is 4 cross arches x 6 shards). Hardcoding 46 would repair the row and leave the mechanism, so instead the banner now says AT LEAST N with its aperture named, and corpus_reconciliation RECOMPUTES at the end where both numbers exist and prints banner 40 + in-run 6 = 46; positive control on the real 40/46 case in tools/testmgr_corpus_reconciliation_devtest.py, 18 rows, all green. WHAT WOULD RETIRE THIS TICKET: a full tier with verdict GREEN and skip_holes == 0. Half is met; the green is EXPECTED from a clean-tree re-run now that terecs4.pp is tagged, and expected is not claimed. WHAT WOULD RETIRE ITS NUMBERS: ls library_candidates/ and df -h/df -i, which move without anyone touching this file. |
— |
| task-t-two-standalone-checks-are-written-and-unwired-price-them-together | T | 35 | task | tools/lowering_passthrough_census.py (frankA, c1961bc63) is written, controlled and deliberately NOT wired into gate.sh -- a new fleet-wide gate step is Track T's to price, not a passing agent's to add. It finds AST kinds whose value arm is a pass-through but which have no arm in IRLowerAddress, the shape that made v := Variant(y) segfault, where a consumer asking for an address silently gets contents. It runs standalone, exits 1, carries two branched-on controls, and wiring it is one line. Its sibling landed (ef96b48f8, the HEAD-side lib/rtl sweep) so this is the remaining half. RECOMMENDED SHAPE, and the one ef96b48f8 used: arm off the MERGE-BASE with origin/master, so committed-but-unpushed counts, and sort failures against the pin rather than keeping an exclusion list. |
— |
backlog-pascal (13)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| bug-p-a-bodiless-procedure-declaration-is-accepted-and-swallows-the-next-routine | P | 40 | bug | procedure A; with no body and no forward is ACCEPTED, and the next routine's body is parsed as A's -- so the parse runs off the end of the file and the error is reported in whatever unit the compiler appends next, naming a symbol that has nothing to do with it. Measured 2026-09-22 on a 12-line repro: the error is undefined variable (UpperCase) in ./compiler/builtin/builtinheap.pas. In a real build it cost an hour: one stray duplicated header line in compiler/emit.inc reported undefined variable (LowerCase) in compiler/paslexer.inc plus nested routine token buffer overflow in compiler/rel8.inc, three files away from the mistake and describing neither of the two things that were wrong. FPC refuses this outright with Forward declaration not solved. The small-file diagnostic DOES carry a good note (it names the appended unit and suggests an unterminated comment); the note is absent when the swallowing routine is in an .inc, which is the case that actually happens. |
— |
| bug-p-a-conditional-set-constant-whose-terms-live-two-units-away-declines | P | 30→85 | bug | The conditional evaluator's probe does not nest -- PasCondProbeUsedUnits refuses at ProbeDepth > 0 -- so a set constant reached through one probe cannot resolve TERMS that need a second. FPC nld.pas:700 asks cs_opt_use_load_modify_store in supported_optimizerswitches; the constant is x86_64/cpuinfo.pas:139, its three terms are globtype.pas:428-430, which is two hops. pxx declines LOUDLY (the right operand of 'in' is not a set constant this pass can read) and that is the correct direction -- a set missing one member answers in with a confident False and takes the other branch. Lifting it is a DESIGN step, not a fix: one save slot becomes a stack, and the ProbeDepth guard that makes the single slot sufficient has to go with it. Banked because it is currently recorded ONLY in paslexer.inc's own comments, where ready --track P cannot see it. |
— |
| bug-p-a-standalone-test-harness-with-string-consts-does-not-compile-under-pxx | P | 30 | bug | test/test_elfdynsym.pas compiles and runs under FPC and is REFUSED by pxx with undefined variable (SDL64) on a program-level string const, and then no overload of ElfSoExportsSymbol matches these arguments — (Integer, ShortString), which says the const was typed as Integer rather than not found. NOT isolated: five reductions all compile clean under pxx — string const after a procedure, with a uses clause, with two var blocks, with an {$include} between the const and its use, and with irregular = spacing — so the trigger is a combination none of them reproduce. The cost today is that this harness cannot go in tools/standalone_inc_harnesses.sh (which builds with pxx) and is FPC-built from its own Makefile row instead; the wider worry is that a program-level const silently becoming an Integer is a wrong-value shape, not a diagnostic one. |
— |
| bug-p-a-var-parameter-accepts-a-narrower-actual-and-writes-past-it | P | 75 | bug | procedure P(var x: Int64) called with a LongInt actual COMPILES, and P then writes eight bytes into the caller's four-byte variable, destroying the adjacent local. fpc refuses the same call: Call by var for arg no. 1 has to match exactly: Got \"LongInt\" expected \"Int64\". Eight-line repro below prints a=-1 guard=-1 -- the sentinel was never passed to anything. This is memory corruption from ordinary, fpc-idiomatic code with NO diagnostic at any stage, and the damage lands on whichever local the frame happens to put next, so the symptom moves between builds and does not point at the call. It also decides OVERLOAD RESOLUTION, and CONDITIONALLY -- which is what makes it hard to reproduce and why the first explanation of it here was wrong: a var parameter's exact-type row is honoured only when EVERY OTHER argument binds with NO conversion at all; ONE by-value argument needing ANY conversion -- widening or narrowing, from a variable or from a literal -- masks it, and declaration order decides instead. That is how lib/rtl/textfile.pas's BlockRead(f, buf, n, c) with c: Integer (the spelling fpc's own charset.pp uses) corrupted the caller's length variable WITH THE EXACT ROW DECLARED. Repaired 2026-09-16 by publishing fpc's four count widths narrowest-first (36/36, fpc 3.2.2 likewise; Int64-first 34/36, fully reversed 33/36, and the exact-length rows survive all three -- which is what isolates the CONVERSION from the order), pinned by test/lib_blockio.pas -- but the ordering is a property of this bug, not a fix for it, and it will stop being needed the day resolution refuses a narrowing/widening var actual. |
— |
| bug-p-compile-time-info-macros-are-not-implemented-and-silently-yield-zero | P | 55→85 | bug | const d = {$I %DATE%}; compiles with NO diagnostic and yields Integer 0; fpc yields the string '2026/09/16'. Same for %TIME%, %FPCVERSION%, %FILE%, %LINE% -- all 0. THE CAUSE IS A SEAM, NOT A MISSING FEATURE: the include pre-pass (ExpandIncludes, compiler/elfwriter.inc) explicitly RECOGNISES {$I %...%} and skips it with the comment leave them in the text for the lexer, and the lexer has no handler for it -- so each side is written as though the other does it, the brace is eaten as an unknown directive, and const d = ; becomes 0 instead of a syntax error. A false premise stated as fact in a comment, which is the one place nobody re-measures. This is the head of 132 of the 207 units of umbrella-pxx-compiles-fpc-itself, ARRIVING IN DISGUISE: it reports as globals.pas:1095 no overload of Replace matches these arguments (AnsiString, ShortString, Integer), which reads like a library gap and is not one -- the candidates are the corpus's own cutils.pas:82-83 and the defect is the THIRD argument, date_string, declared = {$I %DATE%} in version.pas:41. Adding an RTL overload to make that call resolve would make a wrong program compile. |
— |
| bug-p-pchar-of-an-ansistring-cast-of-a-literal-yields-one-garbage-byte | P | 45 | bug | PChar(AnsiString('hello')) answers a 1-byte string whose single byte is the literal's LENGTH where fpc answers hello. No diagnostic. THE BYTE IS NOT GARBAGE -- it is the length prefix read as the first character, so it is deterministic, and the defect CANNOT FIRE BELOW LENGTH 2 (measured: 'abc'->3, 'hello'->5, 'hello world'->11, while '' and any single character agree with fpc exactly). Any regression test must therefore use a literal of length >= 2 and assert on Ord(p[0]), never on the printed form -- a one-character probe certifies the bug as fixed. AnsiString('hello') re-TAGS its node tyAnsiString (23) while the value underneath is still a FROZEN literal, so the tag lies about the representation; PChar's lowering then reads that tag, takes the managed-handle route through PXXPCharOf, and hands it a pointer to a length prefix. Every neighbouring spelling is correct -- PChar(lit), PChar(var), PChar(string(lit)), PChar(expr), s := AnsiString(lit), Length(AnsiString(lit)), WriteLn(AnsiString(lit)) -- so it is invisible to anything but this exact double cast. Found by writing it in a test fixture: it created a symlink whose target was one garbage byte, and the dangling link then failed four UNRELATED assertions in a way that read exactly like an RTL defect. Diagnosed to the node level, NOT fixed: the real question is where a frozen->managed coercion gets triggered, which is a representation seam affecting every cast spelling, not a PChar special case. |
— |
| compat-pascal-the-strict-fpc-flag-family-is-incomplete | P | 15 | compat | RE-SCOPED 2026-09-05 (frankS) after measuring all three named gaps at e6aea92825d0 -- the umbrella WORKS and two of the three items are gone. --strict-fpc and --strict-visibility both refuse cross-UNIT private access where the default accepts, which is the positive control, and it must be CROSS-UNIT: FPC private is unit-scoped, so a same-file probe is accepted under every flag AND under FPC and cannot discriminate. Abs/Sqr WIDTHS is OUT OF SCOPE, not unimplemented -- SizeOf reporting 8 for a double-width intermediate is the operator working, per the settled rule; drop the item. Pointer difference did NOT reproduce: 16 bytes over four Integers, which is what FPC gives for typed pointers -- whoever wants it must name the spelling that diverges. WHAT IS LEFT is ONE item: TypeInfo name, established here only as non-nil and genuinely unmeasured. --strict-fpc/--strict-visibility were missing from --help and are listed now. | — |
| feature-b-rtl-has-no-tdoublerec | P | 25→85 | feature | RE-LANED AND RE-PRICED 2026-09-11, HOURS AFTER FILING, BY ITS OWN AUTHOR -- the slug still says rtl-has-no, kept so citations resolve, and BOTH halves of the original framing were wrong. (1) NOT TRACK B: fpc declares TDoubleRec in rtl/inc/mathh.inc, which systemh.inc INCLUDES, so it is in the System unit and always in scope -- x86_64/cpuinfo.pas:36 writes bestrealrec = TDoubleRec while its interface uses only globtype. Adding a record to a lib/rtl unit therefore cannot fix it; a type in compiler/builtin/builtin.pas is not globally visible either (measured: var r: TVariantRecord in a bare program answers unknown type). This is compiler-side type-name visibility, Track P/A. (2) IT BUYS ZERO UNITS TODAY, PROVED not predicted: cfileutl -- the only unit that reaches this wall -- has implementation uses Comphook, Globals, and globals alone already fails at comphook.pas:251 undefined variable (V_Status). So cfileutl cannot compile whatever happens here. Dropped 40 -> 25 and blocked-by the unit cycle. |
bug-p-a-units-interface-constants-are-invisible-to-a-second-units-implementation-uses |
| feature-p-legacy-value-object-types | P | 15→85 | feature | SUPERSEDED IN THREE QUARTERS NOW, 2026-09-17 -- constructor and destructor SHIPPED at efe06a903 and this ticket is down to two named residuals. pxx hard-errors on BOTH routes to a VMT (an ancestor, and a virtual/dynamic/override/abstract directive), so every object that compiles at all is VMT-less BY CONSTRUCTION and a constructor on one is semantically a plain method -- the refusal was guarding a case that cannot arise here. A Delphi RECORD rule ('a constructor must have at least one parameter without a default value') had to be scoped to records in the same commit: it exists because TR.Create(...) is an EXPRESSION producing a value that would collide with implicit initialisation, while an object constructor runs on an existing instance as a STATEMENT -- and fpc's own versioncmp.pas:35 is constructor invalidate; with no parameters, so that rule applied to objects would have refused the very source this change was for. Pinned by test_object_value_ctor.pas (byte-identical to fpc 3.2.2, parameterless constructor placed MID-LIST not last) and test_object_value_ctor_fail.pas (ONE source, FOUR compiles selected by -d, because every one of these diagnostics HALTS and a single compile would certify three rows without reaching them; a fifth define-less run must COMPILE, which is what makes each refusal attributable to its own row). Corpus 21 / 10 / 176 -> 22 / 10 / 175, one unit (versioncmp), zero regressions. WHAT IS LEFT, both measured and neither implemented: (1) Fail, the standard procedure valid ONLY inside an old-style constructor -- it implies the constructor's hidden Boolean result, which pxx does not have; cmsgs.pas:124 is now the first failure of 3 corpus units and 2 units in the corpus use it (cmsgs, symtable). (2) The extended New(p, Init) / Dispose(p, Done) forms, which DO dispatch through the VMT -- refused by name at all four parse sites as of efe06a903 (previously expected ')' before ','), and used at 67 call sites in fpc's own compiler (rgobj, verbose, browcol, aoptda, nllvmbas). STILL REFUSED DELIBERATELY AND NOT PART OF THIS TICKET'S REMAINING WORK: virtual and inheritance. The 2026-09-16 census is why -- of 35 = object declarations in the reachable corpus, 15 need a VMT, 14 of them are in browcol.pas which NO unit imports, and the 15th is behind an {$ifdef UNITALIASES} defined nowhere. The VMT half is unreachable in this corpus. The decision page decide-old-style-object-types carries a 2026-09-17 note recording that this call was taken there rather than escalated, and what would reverse it. Wired to umbrella-pxx-compiles-fpc-itself; do NOT rank the residuals on unit counts -- that umbrella has sixteen consecutive null rows saying a wall's population is a queue position. |
— |
| feature-pascal-corpus-passrc | P | 30 | feature | Pascal corpus: fcl-passrc — ENDGAME. Deep class hierarchy + resolver (60k src, 40k tests) | feature-pascal-corpus-fpcunit, feature-pascal-corpus-fpjson |
| feature-pascal-management-operators-on-a-class-field | P | 30 | feature | DESIGNED 2026-09-09 (frankS), NOT implemented -- the design section carries the fpc oracle, the two insertion points, and the one hazard that makes the obvious implementation a DEADLOCK rather than a wrong value: the Finalize half must NOT go in PXXRecordRelease (nothing here runs user code is what lets it be called with the heap lock held; a management operator is user code and may allocate -- this is what cb2ed843 was reverted for) but one function up, in PXXClassFinalize's already-unlocked kind-4 pass. Initialize needs a NEW BUILTIN because TDer declaring no constructor has no body to wrap and AN_METACLASS_NEW knows its class only at runtime. Descriptor: a new member KIND (8 is free) is bootstrap-safe by defs.inc's own note where a header field is not, and bytes +8..+15 of a 16-byte member are unread by kinds 1-7. ORIGINAL: c: TCls where TCls has a field of a record with class operator Initialize/Finalize: pxx REFUSES it, naming feature-pascal-management-operators-nested-and-array. It was carved out of that ticket 2026-09-06 because it is a DIFFERENT MECHANISM, not a remaining case of the same one. Measured against fpc 3.2.2: a class field's Initialize runs inside Create and its Finalize inside Free -- an OBJECT lifetime, not a scope one. The desugar that serves records is Initialize(v); try BODY finally Finalize(v) around the declaring routine's body, and applying it here would finalize a live heap object at every scope exit and never run at all for one that outlives the scope, which is worse than the refusal. The insertion points are the constructor and destructor paths, so the shape is closer to how a class's ARC/interface fields are already handled than to anything in the record desugar. CORPUS: fpc testsuite tmoperator4 stops at line 81 on this refusal, and its TA/TB are CLASSES -- that row was mis-attributed to the record nested-field arm, which had no corpus row at all. |
— |
| perf-p-the-pascal-parser-allocates-a-string-per-identifier-token-to-throw-it-away | P | 35 | perf | CHARACTERISED, NOT MEASURED, AND DELIBERATELY NOT OPENED -- banked so it is cheap to pick up later. The shape: CaseEqual(GetTokenStr(idx), nm) allocates an AnsiString through GetTokenStrFromRaw (SetLength + copy) purely to compare it and free it, and CaseEqual's length reject fires only AFTER the string exists, so a wrong-length token pays a full PXXAlloc/PXXFree round trip to learn it was never a candidate. 159 occurrences remain across the Pascal and Rust frontends -- pasparser_prog.inc 93, pasparser_generic.inc 38, pasparser_decl.inc 10, pasparser_proc.inc 8, pasparser_stmt.inc 4, pasparser_expr.inc 1, rparser.inc 1 -- counted by OCCURRENCE, not by grep -c, which counts lines and undercounted this twice on 2026-09-21. The remedy already exists and is proven: TokenCaseEqual in ast_syminfer.inc compares TokChars in place after an integer length reject, semantics identical including the empty/out-of-range edges, and converting the 62 occurrences in pyparser.inc was part of a change that took lekkerzeilen's compile from 105.6s to 92.5s (-12.4%) with BYTE-IDENTICAL emitted output. WHY IT IS NOT BEING DONE NOW: the owner scoped compiler-speed work to NilPy on 2026-09-21 -- 'the 12 second self-build is totally acceptable, we are just worried about nilpy' -- so Pascal parse time is not a worry he holds, and this is banked rather than opened. NOTHING HERE IS A MEASUREMENT OF PASCAL PARSE TIME: no profile has ever been taken of it and the NilPy result does not transfer, because NilPy's cost came from scans that cross the whole import closure and the Pascal frontend may have no equivalent. WHAT WOULD MAKE THIS WORTH OPENING: a profile of a large Pascal build showing GetTokenStrFromRaw or PXXAlloc/PXXFree in the top few symbols. That profile does not exist. Mechanism and the NilPy result: devdocs/perf/lekkerzeilen-build-time.md. |
— |
| task-pascal-conformance-long-tail | P | 15 | task | FPC-conformance long tail: RTL gaps, runtime faults, small parser holes | — |
backlog-decide (51)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| compat-p-system-integer-is-smallint-in-fpc | P | 10 | compat | OPEN DECISION, not settled (owner, 2026-09-02: "i've been pondering that and not came to a conclusive answer"). Track U. SizeOf(System.Integer) is 2 in fpc 3.2.2 and 4 here, because FPC's system unit declares Integer = smallint and the 4-byte Integer comes from the MODE redeclaring it -- so in FPC the qualifier selects a DIFFERENT type and System.Integer(x) truncates to 16 bits. pxx has one Integer and no System namespace. A programmer qualifying a name means THE Integer type, not "narrow this to 16 bits"; reproducing FPC would make an explicit qualification change the semantics of what it qualifies. Narrow: System.LongWord, System.Boolean and System.Char all agree; Integer is the one name FPC's mode shadows. | — |
| decide-a-a-foreign-thread-needs-its-own-tls-block-and-the-bounds-are-the-hard-part | U | 70 | decide | DETECTING a foreign THREAD is solved for the case the shipped I/O-lock discriminator asks about -- the reader's own rsp against the bounds the block's owner recorded, live in ir_codegen.inc. It is NOT sufficient for the question this ticket needs answered. Measured 2026-09-02: a pxx stackful generator body runs on a HEAP stack (lib/rtl/coroutine.pas CoAlloc GetMem(65536)), 13TB from the thread's own frame, so every rsp test reads a running generator as foreign. For the I/O lock that is fail-safe (fall back to gettid); for an IDEMPOTENCE test it is fail-unsafe -- it would install a fresh block on every generator entry and zero the live exception chain, the exact failure this ticket attributes to HI=0, reached by a construct that ships and is tested today. So the open question is where a lazily-installed block comes from AND what marker says "already mine", and the marker cannot be an address. Also measured: the widening-observed-rsp-window option was excluded here on a reason that does not hold (0 of 6 pairwise overlaps with threads using 99.15% of their stacks); it is excluded by the generator stack instead. This blocks a program that fails today: bug-a-the-exception-chain-fix-is-defeated-by-a-libc-pthread. | — |
| decide-a-is-a-pxx-object-a-self-contained-runtime-or-a-translation-unit | U | 55→80 | decide | MEASURED 2026-09-18 (frankB) AND THE FORK COLLAPSES TO ONE SENTENCE FOR THE OWNER: do we promise that a pxx object can supply the runtime for SOMETHING ELSE, or only for ITSELF? Answer "for itself" and B is safe today. Answer "for something else" and A stands and per-object DCE stays off the table until the runtime is shared. Evidence: --dce does not move the export surface at all (307 weak FUNC exported both ways; the exports ARE the root set, so the pass can only remove 266 LOCAL bodies) -- so B had to be simulated, off the object's own relocations, with the compiler's 573-live answer as the positive control (model: 576). Re-rooted at the TU's own two exports, live bodies go 576 -> 78 -- the number this ticket already records for the same code as an executable, reproduced from a different direction -- and 298 of 307 weak exports vanish. 20 of the 298 are routines a backend LOWERS onto and no source names (__pxx_builtin_popcount*, __pxx_va_arg_agg, ...), demonstrated: a second TU containing zero __pxx_ in its source reaches __pxx_va_arg_agg by passing a struct by value through a variadic. So B's contract change is not source-auditable. BUT the pxx-to-pxx pair is NOT at risk: both objects have ZERO undefined symbols of any kind, because each carries everything it reaches -- the parent ticket's own complaint is what makes B safe between pxx objects. The silent breakage is confined to a NON-PXX consumer, or to the libcrtl.a direction where one object provides the runtime for the rest -- both of which are the self-contained-runtime reading, i.e. answer A's premise. Nothing is recommended. The 78 is the load-bearing evidence BECAUSE the two readings fail differently -- compiler-built executable versus a readelf-level BFS on an unlinked object, sharing no code path, and built and controlled before the target number was looked up. x86-64 only; a Pascal object is UNMEASURED rather than assumed-the-same and is the gap most likely to matter, since a Pascal object is what an ESP program actually is; 4b-septies' DATA symbols untouched. NOTE this page carries two UND counts about two different objects -- 20 either way for the xtensa ESP object, ZERO for the x86-64 C TU -- and they do not contradict each other. IMPORT SIDE, separately: ld errors on relocation SITES, not on undefined symbol-table entries -- an xtensa object's ESP-IDF UND entries sit at 20 with and without --dce while relocations naming them go 24 -> 0 and the link goes rc=1 -> rc=0. So "the symbol goes, not just the bytes" is false on the import side and the benefit arrives without it. |
— |
| decide-a-latent-defect-ticket-should-block-the-work-that-makes-it-observable | U | 55 | decide | f4fb9d31b made generic constraints load-bearing while bug-p-generic-constraints-are-checked-before-the-type-section-closes sat open at p40 describing exactly why the placement was wrong. The regression was predicted in writing before it happened, and nothing in the board could express the dependency. Same shape hit three times on 2026-08-30. Options: a new edge type, a convention on blocked-by, a check in tools/progress.sh, or accept it. | — |
| decide-a-repro-line-in-a-ticket-is-not-a-command-anyone-has-run | U | 45 | decide | decide: should the handbook say that a repro line in a ticket is not a command anyone has run? | — |
| decide-a-the-smallset-mechanism-is-built-and-green-does-that-change-the-park | U | 60 | decide | OPEN DECISION for the owner. The owner parked the 4-byte set at 18:00 on 2026-09-02 ("our sets are just always 32 byte") on a COST judgement — the ticket's own words are that the mechanism was designed but the overhead was judged not worth it now. The overhead has since been paid: the owner's own smallset design (a hidden second type kind, tySmallSet, same set keyword in source) is IMPLEMENTED, self-hosts, and matches the FPC 3.2.2 oracle byte-for-byte on x86-64 + i386 + aarch64 + arm32 + riscv32, with gate.sh quick GREEN and 50/50 unchanged rows on a 57-test set corpus. It is NOT landed — the park is a live decision and this is a fork of intent, so the work is banked as devdocs/dev/parked-patches/smallset-4-byte-set-storage-class.patch (22 files, applies clean at ff62bb870) and the tree is back at 32 bytes. The question is only whether the measurement changes the park; it does not re-litigate the reasoning. If the answer is no, delete the patch and this ticket and keep the rainy-day ticket as the record. |
— |
| decide-a-what-a-set-costs-bits-bytes-bounds-and-what-file-of-t-writes-to-disk | U | 40 | decide | OWNER RAISED THE FORK: a byte-per-element set may save instructions over a bitset, against compactness winning for file IO and low-memory targets like ESP. FIRST, A PREMISE CORRECTION THAT CHANGES THE QUESTION: we are ALREADY bitpacked. 32 bytes is a fixed 256-BIT width, one bit per ordinal over 0..255 — not a byte per element (defs.inc:2003 { 21: Set — 32-byte bitset }; IR_SET_LIT bakes a 32-byte MASK). The four-type-sizes ticket's sets are not bitpacked was false and is corrected. So the live fork is NOT bits-vs-bytes for storage; it is (a) whether the WIDTH follows the declared bounds, and (b) what a set looks like ON DISK once file of T exists, because that is where a representation stops being an implementation choice. RECOMMENDATION: keep bits, narrow the width, and decide the on-disk form SEPARATELY from the in-memory one. Bytes lose on set algebra by 8x and the one case they could win — single-element membership on a target with no bit-test instruction — is already compiled away, because c in ['a'..'z'] against a LITERAL lowers to comparisons and never builds a set at all. |
— |
| decide-a-what-is-a-plain-frozen-strings-capacity-255-or-eight-megabytes | U | 45 | decide | OPEN FORK, and everything about the frozen string model's sizing hangs on it. A plain frozen string (-uPXX_MANAGED_STRING) is ALLOCATED at three different sizes -- 8388616 bytes for a global, 264 for a local, 264 for a record field -- and CLAMPED at 255 in every one of them, by assignment as well as by concat, measured. So 8388352 bytes of every global plain string are unreachable by construction, and a dynamic array of them strides 8 MB per element and SEGFAULTS at 1000 elements on i386/aarch64/arm32/riscv32 while x86-64 refuses the shape outright -- ALL OF IT REPRODUCED ON PIN v401, none of it new. Two readings and they lead opposite ways: (A) the ALLOCATION is intent -- STRING_CAP is named, commented { 8 MB } and used at four sites, LOCAL_STR_CAP's comment says max string length for local/stack variables, so capacity is meant to be storage-class-dependent and the flat DEFAULT_STR_CAP clamp is the defect; or (B) the CLAMP is intent, 255 is the type's capacity, and every allocation above 264 is dead space. Not decidable from the code: both are internally consistent and each has a constant written as though it were the answer. Blocks feature-a-dynamic-array-of-frozen-strings, whose element stride IS this number. |
— |
| decide-c-crtl-rand-max-is-conforming-but-breaks-real-code | U | 40 | decide | crtl defines RAND_MAX as 32767 and rand() returns [0,32767]. C99 7.20.2.1 only requires RAND_MAX >= 32767, so this is conforming — but every mainstream libc uses 2147483647 and real programs branch on the value. busybox editors/awk.c has an #error for anything else and is the only busybox file still blocked on a non-library gap. Raising it is a behaviour change to a shipped library, not a defect fix, so it is a call to make, not a bug to close. | — |
| decide-c-should-a-libc-symbol-from-an-unresolvable-header-bind-to-libc | U | 55 | decide | An import whose derived soname no library answers to is REFUSED, even when the compiler can see exactly which library exports the symbol. WIDENED 2026-09-10 past the libc case it was filed for: the general question is whether to resolve such an import by asking who exports the symbol. MEASURED, and it makes the fork decidable — over 1439 native libraries, 6 of 8 sampled symbols resolve to exactly ONE library (crc32, compress, deflate, inflate -> libz alone; sqlite3_open -> libsqlite3; png_read_png -> libpng16), and where several answer they are VARIANTS OF ONE LIBRARY, never unrelated ones: SDL_Init -> libSDL-1.2/libSDL2-2.0/libSDL3, curl_easy_init -> libcurl/libcurl-gnutls. So the risk is not 'any library could claim any symbol'; it is choosing between versions of the right library, which the header's directory already disambiguates for SDL2. The honest shape is therefore search, and ERROR naming the candidates when more than one family answers. | — |
| decide-crtl-s-FILE-table-is-an-unguarded-test-then-set-and-no-probe-has-caught-it | U | 40 | decide | __crtl_alloc_file (lib/crtl/src/stdio.c) picks a FILE from a 16-entry table with if (!files[i].heap) { files[i].heap = 1; return &files[i]; } — test-then-set, no atomic, no lock, and stdio.c's own comment at line 1181 says outright that crtl's FILE has no lock and the flockfile family is a no-op for single-threaded streams. Two concurrent fopen() calls can therefore be handed the SAME FILE*. MECHANISM CLEAR, RACE NOT OBSERVED: two probes failed to produce it and BOTH failures are recorded below so nobody re-runs them. THE FORK IS NOT TECHNICAL, which is why this is a decide and not a bug: do we want crtl's stdio to be thread-safe at all, or is it a single-threaded runtime whose flockfile no-ops are an honest statement of that? If the first, this is one cmpxchg and the FILE lock stops being a no-op; if the second, the answer is a documented refusal and nobody should spend another probe on it. Measured today: pthread_create IS trampolined and errno IS per-thread, so threaded C on pxx now runs, which is what makes the question live rather than theoretical. |
— |
| decide-do-we-introduce-the-named-trade-off-flag-axis-and-what-is-the-bar | U | 55 | decide | The O charter names -Os, -Ofast and -funroll-loops as the shape a trade-off takes -- an author chooses WHICH trade, not HOW MUCH -- but NONE OF THE THREE EXISTS. No named trade-off flag has ever been built, so the axis is described and empty. A first candidate now exists: the x86-64 static-literal retain guard, measured -3.96% on literal-heavy code and +6.95% on the real workload (compiler.pas), i.e. a genuine trade rather than a win or a loss. The fork: do we open the named-flag axis at all, and what is the bar for putting a pass behind one -- given that PROMISE (delivered value, measured) and PROOF (Track T full tier) were ruled for the -O levels and a trade-off flag by construction cannot show net promise? Filed by the coordinator at frank-optimize's request; frank-optimize declined to file it while holding the candidate. | — |
| decide-does-gate-before-you-commit-survive-when-its-only-justification-is-false | U | 40 | decide | CLAUDE.md's GATE BEFORE YOU COMMIT, NOT AFTER rests entirely on the claim that quick's FPC seed canary only fires on an uncommitted tree. tools/gate.sh arms it against the MERGE-BASE, so a committed-but-unpushed tree — and a tree whose compiler/ has moved since the last green seed — still gets full FPC coverage. The false sentence is being corrected separately; this ticket is only the question of whether the INSTRUCTION should survive its justification. Rank on the MEASURED friction (over-gating, observed), but the hazard is two-directional: the same false belief read the other way is a live under-gating mechanism, unobserved and — because nothing durable records working-tree state at gate time — unobservable after the fact. |
— |
| decide-is-a-host-sdk-scanner-still-wanted-now-that-nothing-needs-one | U | 25 | decide | feature-dynamic-include-paths-config is the oldest open ticket (2026-06-14) and its big half landed in four slices. Its three remaining bullets were parked on 2026-08-31 as lacking a named consumer. Re-measured 2026-09-01 rather than re-asserted: none has one, and two are near-zero value as specified — the soname fallback table is UNREACHABLE on a normal Linux host, and an xtensa build needs no generated config. What is left is intent only: does the owner still want a scanner tool, or should the bullets be cut so the oldest ticket in the tree can close? | — |
| decide-is-binds-the-cpyext-runtime-the-ratified-extension-module-check | U | 30 | decide | decide-nilpy-import-rule-vs-a-cpyext-extension-module ratified PyInit_<name> as the extension-module criterion; the implementation substituted 'the unit binds the cpyext runtime' after measuring that PyInit_<name> holds for only 3 of the 6 real units, and flagged the deviation for the owner to overrule. Nobody overruled it either way, and it is now shipped, pinned in v391, and — as of this ticket — documented on the public website. Ratify the substitution or order it changed. |
— |
| decide-linking-a-so-as-if-it-were-an-object | U | 20 | decide | DECIDED NO, 2026-09-17, and filed so nobody rediscovers it. Raised by the owner while scoping pxx --link. A .so has ALREADY BEEN LINKED and the information a static link needs was consumed and discarded on the way: its relocations are DYNAMIC (.rela.dyn/.rela.plt — instructions for ld.so at load time, not for a linker at build time), its symbols are in .dynsym while the .symtab and .rela.text a static link needs are usually stripped, it is position-independent and already laid out so there is no per-function granularity to select from, and copy relocations, symbol interposition, version records and init order across the DT_NEEDED chain are loader semantics with no static equivalent. Tools claiming to do this are approximating and break on the general case. THE REAL QUESTION UNDERNEATH IS DIFFERENT AND ALREADY ANSWERED: 'I have a .so, no source and no .a, and I want to use it' is served by staying DYNAMIC, which pxx already supports on the producer side — elfwriter.inc emits DT_NEEDED (line 236) and a dynsym (314), and the weakexternal path (157-167) lets a library reached only by weak imports contribute NO DT_NEEDED at all, so a weak-only program collapses back to a static link. So: no, and the alternative is not a compromise. |
— |
| decide-may-exports-name-a-routine-that-is-not-cdecl | U | 40 | decide | exports Foo; where Foo is not cdecl. FPC exports it under its own calling convention, which for pxx means callable and wrong from outside. Three answers: reject, imply cdecl, or export under the pxx convention. Track P shipped the REJECT arm because it is the only one that can be relaxed later without breaking a program that already compiles — this ticket is the relaxation question, not a blocker. |
— |
| decide-n-what-does-dunder-file-mean-for-a-module-inside-a-package | U | 60→90 | decide | A compiled NilPy module inside a PACKAGE reports file with the package directory COLLAPSED — <exe_dir>/world.py where CPython says <root>/lekkerzeilen/world.py — so dirname(dirname(abspath(__file__))), which is how a packaged module names its repo root, overshoots by one level. Measured 2026-09-12: lekkerzeilen's runtime package has FOUR file sites and ALL FOUR are that two-dirname form, all naming the same data root, and all four come out wrong; --starts then prints open water: nowhere in particular instead of listing the region, with nothing raised. This is NOT a re-litigation of decide-nilpy-dunder-file-for-a-compiled-program: that ticket never mentions packages, its rule says <exe_dir>/<original module basename>, and its stated payoff (dirname(abspath(__file__)) yields the executable's directory for every module) is only reachable by collapsing the package — which is exactly what breaks the other idiom. It also deferred an application data root until something needs it, and lekkerzeilen is the first program that does, so its own trigger has fired. THE FORK IN ONE SENTENCE: do we want a compiled program's modules to find their data laid out the way the SOURCE tree is, or laid out the way the shipped binary's directory is? Both options are stated below with what each costs, and there is a third (a data root) the earlier decision already sketched. |
— |
| decide-nilpy-deepcopy-over-the-container-subset | U | 40 | decide | copy.deepcopy: implement over the subset, or keep the loud absence? |
— |
| decide-nilpy-ranking-is-shaped-by-a-low-dependency-sample | U | 55 | decide | A fourth-corpus probe (reportlab 4.2.5, 421 .py) at pin v389 found that NONE of its 30 distinct first walls is a wall the webencodings/html5lib/tinycss2 family produced — because 89% of its failures are missing library surface and it never reaches the mechanism layer. The family's mechanism walls are not wrong, they are CONDITIONAL: they are what a corpus hits once its import surface is already covered. The three corpora that generated the whole 55-70 ranking are self-contained web parsers with almost no stdlib footprint. On a corpus with an ordinary footprint, landing the entire mechanism cluster would move compile count by ~zero. prio: is the human's field, so the re-ranking call is the owner's. | — |
| decide-openbsd-pinsyscalls-vs-the-rt-sigreturn-residual | U | 25 | decide | MEASURED, both halves, on x86-64 Linux. NON-THREADED: --rtl-libc takes a Pascal binary 75 -> 1 raw kernel entries, and --no-signals gives 0, so the residual is exactly one rt_sigreturn (ir_codegen.inc:585, emitted via the syscall_raw mnemonic that is never thunked). THREADED (frankS, re-measured independently by frank-rust on test_atomic_counter): 142 -> 4 -> 3, and the three are the clone stub, INDEPENDENT of the rt_sigreturn rather than one instruction double-counted. So the floor is FOUR, not one, and each is irreducible for its OWN reason: rt_sigreturn restores context from a signal frame at a fixed offset from rsp; SYS_clone's RETURN is the point (a wrapper returns into a frame the child does not have); arch_prctl installs GS before TLS exists; and the child's SYS_exit has no frame to return to. None is a missed thunk routing. THE FORK: does OpenBSD pinsyscalls accept a binary whose kernel entries are these four? feature-port-openbsd-libc's acceptance says "disassembly contains no raw syscall" and its own item 3 says that criterion is wrong -- this is that decide. Options: (a) signals off, measured to give 0 non-threaded but still 3 threaded, so it does NOT reach zero; (b) route sigreturn via libc's sigreturn and the clone stub via libc's own thread creation -- recommended, but it is a per-OS contract, not a flag; (c) restate the criterion as PINSYSCALLS COMPLIANCE rather than zero raw syscalls, which is worth doing whichever way the rest lands, because a binary can have four raw syscalls and comply. | — |
| decide-p-a-terminal-folder-that-is-unranked-is-the-wrong-home-for-a-measurement-that-explains-a-live-red | U | 30 | decide | FORK, not settled. bug-p-a-generic-routines-implementation-type-parameters-are-not-checked-against-its-interface reproduces at HEAD and the scope rule points straight at rejected/ — a type parameter renamed between a routine's interface and its implementation is produced by a mistake and nothing else, and CLAUDE.md is explicit that us accepting what FPC rejects is not a defect. What blocks the rejection is that its own summary names TWO LIVE CONFORMANCE FAILS (tgenfunc17.pp, tgenfunc18.pp, in an otherwise 347/2 run), and rejected/ is not ranked — so rejecting leaves two red rows with nothing pointing at why, and the next reader files it again. Options: (a) known-incompat/ with the two rows cited as expected FAILs; (b) implement the check anyway. Generalises past this ticket: a terminal folder that is LOADED but UNRANKED is the right home for a wrong report and the wrong home for a correct measurement that explains a live red. |
— |
| decide-posix-master-vs-fpc-named-master-for-the-socket-facades | U | 25 | decide | Posix.* is master, or the FPC-named units are? The tree has already answered, the other way |
— |
| decide-release-signing-key-custody | U | 25→80 | decide | feature-release-checksums-repro sits at the head of Track A's queue and cannot be finished by an agent: signing a release needs a PRIVATE KEY the user generates and holds, and a public key committed to the repo. Which tool (minisign vs GPG vs sigstore), who holds the secret, and where the public half is published are all human calls. The checksum and reproducible-build halves are agent-work and are listed below as what to do once this is answered. | — |
| decide-shift-native-width-was-never-re-confirmed-on-the-full-table | U | 45 | decide | decide-shift-operator-promotion-width (2026-08-10) ruled that shifts happen at NATIVE width. The next day its implementer measured that the cost table the user was shown listed ONE divergence from FPC and the real number is four, wrote 'the call is worth re-confirming rather than assuming', and filed nothing — the note lives inside a file in decided/, which by construction nobody re-opens. Two agents have since hit the divergence in the wild and filed it as a bug. One question for the user: keep native width for a DECLARED narrow variable, or promote only UNTYPED operands. Untyped operands are already settled and are not in scope. | — |
| decide-should-a-failed-compiler-build-delete-its-target | U | 40 | decide | A failed make compiler/pascal26 leaves the PREVIOUS binary on disk -- the Makefile has no .DELETE_ON_ERROR -- so the probe you run next executes the code your change was replacing and prints a plausible correct answer. Measured 2026-09-06: frankS nearly certified a positive control on output produced by the previous build. It is a fifth route to a stale binary beside CLAUDE.md's four, and the only one CAUSED BY THE THING BEING TESTED, so the failures are perfectly correlated -- the worse the change, the more certain you are to measure the old compiler. THE FORK: add .DELETE_ON_ERROR (a failed build leaves NO binary, so the wrong measurement becomes impossible rather than merely discouraged) versus leave it and rely on a one-line rule. THE COST IS THE REASON THIS IS A DECISION AND NOT A FIX: every failed edit would then cost a pin-seeded rebuild instead of a 12-second one, and a failed edit is the COMMON case in this loop, not the rare one. Fleet-wide trade, nobody's lane to take alone. Recommendation: the rule, not the Makefile change -- held WEAKLY, because the two rates are not commensurable: A's cost is certain and measurable while A's benefit is measured by SELF-REPORT, which is blind precisely to the cases where the hazard did its damage (a seat that does not catch it files a CONCLUSION, not a correction). Two instances in one day is a floor over the visibly-failed subset, not a rate. |
— |
| decide-should-a-python-program-that-imports-threading-compile-as-written | U | 55 | decide | > | — |
| decide-should-the-full-suite-hook-match-argv-rather-than-the-whole-command-string | U | 40 | decide | .claude/hooks/no-full-suite.sh matches the whole Bash command string, and a heredoc commit message is part of that string. So a commit message that NAMES a full-tier recipe in order to explain that it was NOT run is refused -- and so is a markdown document quoting a corpus glob. Two independent instances on 2026-09-05, neither running any test. The cost is not the keystroke: the hook penalises precisely the practice CLAUDE.md asks for, which is writing the gate justification into the commit message, and the workaround (-F a file, or the Write tool) is invisible to the next person, who learns only that mentioning a tier in prose is painful. NOBODY MAY NARROW THIS ON THEIR OWN JUDGEMENT -- it is permission machinery and the direction of the change is 'less strict', so it is an owner call. The fork: match argv only, keep matching the whole string, or exempt the commit-message path. |
— |
| decide-should-writeableconst-off-be-honoured | U | 20 | decide | {$WRITEABLECONST} is not implemented at all — the compiler contains no reference to it. Typed constants are now unconditionally writable, which is FPC's DEFAULT; the question is whether pxx should honour the OFF form and refuse the store, or document typed consts as always writable. A dialect call, not a bug fix. |
— |
| decide-state-the-population-beside-the-number-and-make-a-probe-s-identity-as-fine-as-its-decision | U | 60 | decide | decide: two CLAUDE.md rules proposed from the canary pass — state the POPULATION, and match a probe's IDENTITY to its decision | — |
| decide-t-per-assertion-subjects-or-accept-the-file-level-label | U | 25→50 | decide | The float-red labelling mechanism is live but has zero adopters, and structurally cannot gain any: it labels a whole JOB, while every file that motivated it mixes last-digit accuracy with a NaN fault, a missing name, an 84-ulp regression or a formatting bug. The only remaining shape is per-assertion subjects -- real machinery in T's tooling plus a pass through N's files, entirely in service of the subject the owner has called low prio by definition four times, and whose motivating reds have not appeared in 259 runs. Recommendation: accept the file-level label as future-only, build nothing more. | — |
| decide-t-should-a-skip-close-an-open-regression | U | 25 | decide | reg_open counts red -> skip as FIXED, so a regression closes when a box merely STOPS RUNNING the job — the mirror of the skip-as-last-good bug, pointing the other way. It is a deliberate existing trade (the alternative pins a regression open forever on a box that cannot run the job), so it is a policy call, not a defect. Split out of 0dec0194a rather than bundled, because a policy change smuggled in behind a bug fix is how the trade would have been lost without anyone deciding it. | — |
| decide-t-the-full-suite-hook-refuses-prose-about-the-suite | U | 55→65 | decide | no-full-suite.sh matches command TEXT, so writing ABOUT a suite is refused as running one. RE-COUNTED 2026-09-06: at least TEN instances across five-plus sessions, not five -- two further Track T tickets were filed independently on 2026-09-05 reporting the same cause, which is itself the finding, since this row was written to stop exactly that rediscovery. AND THE COUNT CHANGES THE FORK: option 3 (downgrade for git commit) covers only a MINORITY of the measured instances -- three of frankC's five are cat > file / cat >> LOGBOOK.md heredocs that write FILES, and a separate rule (the shell-loop one) fires on the word for plus a test/ glob inside a heredoc body, with a two-line repro. Every single instance is an author WRITING DOWN WHAT THEY DID, which is the sentence the hook's own refusal text instructs them to write. Any fix edits .claude/hooks/, which binds every agent on this box, so it is the owner's call and no agent may make it. |
— |
| decide-the-free-section-index-is-documented-and-unused | U | 45 | decide | NUMBERS UPDATED 2026-09-06 (fe0c7e2cd): the playbook is 905KB / ~225k tokens / 237 sections, not the 279KB / 70k / 72 quoted below -- it TRIPLED while the pointer stood still, which is this ticket's own thesis happening to it. Fork unchanged. CLAUDE.md tells every session the debugging playbook is large and to LOOK UP THE SECTION, and states that grep '^## ' lists the 72 sections free. The cost warning is vivid and the free-index clause is subordinate, and the observed result is that the cost-avoidance rule over-applies from 'do not read the file' to 'do not consult the file'. Three measured instances in one night, two of them by the sessions that wrote the surrounding rules. This is a WORDING fork for the owner, not a proposal to restructure the playbook. |
— |
| decide-the-one-target-rule-should-name-the-build-host-not-only-the-architecture | U | 45 | decide | CLAUDE.md's NOTHING OBSERVABLY DIFFERS IS A CLAIM ABOUT ONE TARGET names the TARGET ARCHITECTURE and says the default place anyone looks is the 64-bit host. Measured 2026-09-05: the same failure occurred with the variable one level further out -- the BUILD HOST'S INSTALLED PACKAGES. Five regressions were closed GREEN at HEAD on plexus, which has the GTK dev headers, while the failure was on seven, which did not; a host with the headers passes whether or not a bug exists. The author ran the full job rather than the failing step AND measured at HEAD rather than the filed sha -- the two moves that normally rescue you -- and neither could have caught it, because THE DISCRIMINATOR WAS NEVER IN THE TREE. Proposed text below; this is a CLAUDE.md change and therefore the owner's. NOT to be edited in by any agent. |
— |
| decide-the-proof-grade-gate-is-unsatisfiable-on-the-host-that-does-the-sweeping | U | 55 | decide | CLAUDE.md's -O3 promotion gate defines proof as a full run with skip_holes == 0. Measured 2026-08-31 over seven's whole archive: 121 full-tier runs, 120 with skip_holes=1 and one with 2 — NONE at 0, ever. The hole is a permanently unrunnable rdrand job, and it is structural: seven is dual E5645 (Westmere, no RDRAND) while plexus has it. Since Track T moved to seven on 2026-08-29, the gate as written can never be met, so NO -O3 pass can ever be promoted. Needs a ruling on what proof-grade means in the presence of a permanent host hole; recommendation is an enumerated per-host allowlist so a NEW hole still fails. PREMISE PARTLY OVERTAKEN BY EVENTS 2026-09-16: the sweeping host is BORG now, not seven (plexus retired to borg 2026-09-11), and borg SATISFIES the literal gate -- 114 full-tier runs since the handover, every one at skip_holes=0, and ZERO borg full runs at skip_holes>0, ever. So the gate is no longer unsatisfiable where the sweeping happens and NO promotion is blocked by it today. The fork is NOT closed: it was always about what proof-grade MEANS in the presence of a structural host hole, and that question survives a host move -- it just stops being urgent. Re-read option 1 in this light: it costs nothing today, which it did not when this was written. |
— |
| decide-the-reflog-attribution-rule-in-claude-md-misses-the-majority-of-commits | U | 55 | decide | CLAUDE.md's attribution method (git -C ~/<name> reflog --format='%h %gs' | grep '^<sha> commit') is CORRECT and its stated RATE is wrong: measured 2026-09-06 over 719 commits and 17 checkouts, commit alone resolves 43%, while commit plus the rebase (pick) family resolves 79% with zero ambiguity. Cause is tools/sync.sh, which pulls --rebase before every push: an authoring checkout's own commits are REPLAYED, so the sha that reaches origin/master carries rebase (pick): in that checkout's reflog while the commit: entry stays on the pre-rebase id. The miss is therefore structured -- highest on the MOST RECENT commit, which is the one anyone is asking about -- and the file's 'the EIGHTH sha not resolving is the instrument telling you it has a failure mode' reads as occasional when it is the majority. DO NOT widen to any rebase: rebase (start) checks out the UPSTREAM tip and stamps every puller's reflog, which lifts resolution to 53% and makes 289 of 719 shas name TWO seats -- a confident wrong answer wearing the shape of a better one. Text proposed below, NOT edited in; CLAUDE.md is the owner's file. The tooling half is already landed (732b238d7, whoholds prints seat=). |
— |
| decide-the-utf16-payload-fact-is-spelled-twice-kind-widestr-and-enc-ucs2 | U | 55 | decide | The runtime already spells 'this block holds UTF-16' TWICE: PXX_KIND_WIDESTR = 5 in the BlockKind byte, stamped at three sites in builtinwide.pas and shipped, and the reserved PXX_ENC_UCS2 in KindData0 that feature-a-stamp-and-read-the-managed-string-encoding-field was filed to start stamping. Neither is READ by anything, so nothing is broken yet and this is the cheap moment. Recommend the ENC field wins and WIDESTR retires, because kind is one byte with one value and so TEXTSTR (NilPy, character positions) + UTF-16 payload has no representable value today. Do NOT implement that ticket's gap 2 until this is ruled. | — |
| decide-two-devdocs-directories-make-a-wrong-grep-look-like-a-refutation | U | 30 | decide | devdocs/dev/ (50 files) and devdocs/developer/ (58 files) both hold internal developer docs. A grep in the wrong one returns silence, which reads like a refuted citation rather than a mislocated file. Decide whether to consolidate, and if so which name wins, given 631 citations point at dev/ and 40 at developer/. | — |
| decide-two-threading-docs-disagreed-for-seven-weeks | U | 40 | decide | Two threading docs, one subject: consolidate, or make the split explicit? | — |
| decide-u-do-the-measurement-rules-want-a-home-that-every-seat-reads | U | 60 | decide | Do we want one set of measurement rules that every seat on this machine reads, or does each project's rule file stay independent? | — |
| decide-what-a-static-python-program-on-a-microcontroller-needs-to-write-to | U | 50 | decide | Every NilPy ESP image carries FIVE 4,128-byte text records -- Input, Output, ErrOutput, StdOut, StdErr -- totalling 20,640 B, which is 23% of its .bss and 16% of its whole 125,832 B of SRAM, in a program that never opens a file and on a chip where SRAM is the scarce resource. Measured 2026-09-20 with PXXDBG=a.datamap on examples/esp32/nilpy-c3. THE FORK IS NOT TECHNICAL AND THAT IS WHY IT IS HERE: what should a static Python program on a microcontroller be able to write to? Every answer is implementable and they differ in what we are trying to be -- a Python whose programs behave the same everywhere, or an embedded target that gives you what the chip can afford. Options: (a) keep all five, standard behaviour, 20,640 B; (b) keep stdout/stderr only, drop Input/Output/the file machinery on ESP; (c) shrink the per-record buffer on ESP (4,128 B is a 4 KiB buffer plus header, sized for a filesystem, not a UART); (d) allocate a record's buffer on first use so an unused stream costs a header. Recommendation: (c)+(d) -- it keeps every program working, and a line-buffered UART does not need 4 KiB. | — |
| decide-what-should-a-shared-gate-do-when-its-watched-number-grows-from-normal-work | U | 50 | decide | tools/exit_observable_devtest.py fails when the count of stdout-only cross-target rows exceeds a high-water mark. AMENDED 2026-08-30 by frankT (904b26bd9) AND THE AMENDMENT CUTS AGAINST THE ORIGINAL RECOMMENDATION -- read the addendum before deciding. The filing measured 531 -> 551 in six hours from three lanes' normal work and leaned toward option 3 (report the drift, stop failing) on the grounds that a standing red nobody owns is the state a red exists to prevent. Re-measured per Makefile commit, the count peaked at 559 and then a lane paid it back to 531 EXACTLY within six hours (d9d166f7e, Track A; bug-a-twenty-new-cross-target-rows-compare-stdout-without-the-exit-code is in done/). So the red WAS owned and paid, and the ratchet is what applied the pressure. RESIDUAL NOW CLOSED (7d1f9aeb4, A+S, 21:58): the 2 xtensa exception/unwind rows assert the exit code on both sides, the count is back to the 531 mark and tools/exit_observable_devtest.py is GREEN -- so the ratchet has now been paid down twice by the lanes that moved it, which is evidence for option 1 and against the filing's option-3 recommendation. THE FORK IS STILL OPEN AND STILL THE OWNER'S: nobody has bumped the mark or resolved it, and the question -- what a shared gate should do when its watched number grows from other lanes' normal work -- is unchanged by the instance being clean. | — |
| decide-what-should-pxx-selfcheck-assert-when-the-compiler-cannot-spawn | U | 30 | decide | Five of the six CLI/UX flags are landed; --selfcheck is the last, and its blocker feature-release-packaging is now in done/. The spec defines check 1 as pxx -> gen1, then gen1 -> gen2, cmp gen1 gen2 -- a real fixedpoint STEP, which requires running the freshly built binary. The compiler binary cannot spawn a process and locates itself only via ExeDir. Every in-process alternative asserts something WEAKER under the same trusted name. tools/selfcheck.sh already does the specified thing and already ships in the release tree, so doing nothing is a real option. |
— |
| decide-where-the-string-delete-and-insert-routines-should-live | U | 35 | decide | lib/rtl/sysutils.pas declares Delete(var s: AnsiString; index, count) and Insert(const src: AnsiString; var dst: AnsiString; index) -- two routines fpc keeps in system and not in sysutils -- and both bodies are byte-for-byte what the __pxxStrDelete/__pxxStrInsert intrinsic already does. Their only effect is to shadow the intrinsic, which cost dyn-array Delete/Insert for every program that uses sysutils (narrowed at f5ad23c32). RESOLVED SAME DAY AND THERE WAS NO FORK: the ESP cost I declined to trade against does not exist. frankH measured it at HEAD -- on the bare ESP profile uses sysutils does not compile AT ALL (sysutils drags lib/rtl/strings.pas, whose UpCase needs the same builtin unit), so nothing on ESP can reach these declarations, and the posix ESP profile HAS the builtin unit so the intrinsic works there. Removal owned by frankH. |
— |
| decide-which-way-the-wasi-capability-model-should-point-once-it-has-one-owner | U | 25 | decide | compiler/builtin/wasibackend.pas and lib/rtl/platform/wasi/platform_backend.pas each carry their own preopen table and rights logic. Both work, so nothing is red -- and a duplicated CAPABILITY model fails silently, as one path opening files the other refuses with ENOTCAPABLE. De-duplicating is not a typing job: a shared include double-defines when both units co-occur in one program, wasibackend cannot use the PAL by design, and the remaining direction points a lib/rtl unit at compiler/builtin, backwards from every other dependency in the tree. That is a layering call, not an implementation detail. | — |
| decide-who-reads-progress-sh-check | U | 55 | decide | tools/progress.sh check works, reports correctly, and has no reader. It had been printing a p60 as invisible-to-the-ranker for days (STALE-EDGE-HIDDEN + BLOCKED-BY-REJECTED on perf-p-parsefactorcore) and nothing acted. The fork is WHO reads it, and it splits: the MECHANICAL classes (a ticket hidden from ready/next by a blocker that is closed, rejected or nonexistent) need no judgement and could be wired into ready/next; the JUDGEMENT classes (STALE-PARK, NEAR-DUP, DEAD-COMMIT, PROSE-EDGE) cannot be automated at all, because STALE-PARK matches SLUGS not QUESTIONS. Recommendation: wire the mechanical subset, leave the rest on-demand, and give STALE-PARK-HELD a router. Not filed as a Track T defect: the tool is not broken. |
— |
| decide-whose-job-is-it-to-notice-a-ticket-has-gone-stale | U | 50 | decide | FIVE stale ticket SUMMARIES were found by hand in one day (2026-09-19) across two lanes, each with a correct body — the part everyone reads contradicting the part nobody scrolls to, and the summary is what carries prio into the ranker, so a stale one promotes dead work to the top of a queue. Before proposing a tool I measured whether detection is possible and IT LARGELY IS NOT, for a reason that kills every future grep proposal: PARTIAL COMPLETION IS THE NORMAL CASE AND IS TEXTUALLY INDISTINGUISHABLE FROM STALENESS — a body-says-done heuristic has recall 1 of 5 on the real cases and flagged 7 of 586 open tickets, ALL SEVEN correctly open with accurate summaries reading "FIXED piece 1 of the three" and "FIXED AT HEAD, STILL WRONG IN THE PIN" (precision 0). The four misses fail for THREE DIFFERENT reasons and only one is textual. The one class that looked mechanically checkable — a summary quoting a compiler diagnostic verbatim — FAILED ITS POSITIVE CONTROL: run against the riscv32 ticket it was designed from it answered not-flagged, because the quoted string is a generic template still in compiler/ with only the one arm fixed. Age is no signal either: nothing open is over 19 days and the top tickets are 0-12 days old, because this tree takes ~250 commits a day — staleness here is VELOCITY, not neglect. THE CHEAP HALF IS BUILT AND DID NOT WAIT FOR THIS TICKET (claim now prints the summary with a last-verified date, progress.sh verified <slug> records one, and claim deliberately never stamps it — guarded by a positive control). WHAT IS LEFT FOR THE OWNER IS A COST, NOT A DESIGN: do we want to spend model time, every day, having something read every open ticket against the tree — or is that the job of whoever picks the ticket up? A standing pass over 586 tickets is the only approach measured to work and it is a permanent token commitment, which is his dial. Also measured, independent of the fork: progress.sh check already answers the system side partly and emits 45 findings / 36,395 bytes, 20 of them NEAR-DUP — a 36KB report is not read, so a family with precision 0 makes it 36KB plus noise. AND THE TOOL IS ALREADY SPECIFIED AND UNBUILT: bug-t-check-has-no-aperture-for-a-ticket-whose-body-records-its-own-completion sits at p60 proposing exactly the body-says-done aperture, filed twenty days ago off a real dispatch loss — the numbers above ARE its measured yield, so it should be read before it is built rather than promoted. |
— |
| decide-widening-to-the-group-sends-every-agent-to-the-same-folder | U | 55 | decide | CLAUDE.md tells every agent to take what next names and pull in its neighbours. When the neighbours are a SUBSYSTEM that works. When next names an auto-filed regression, the only neighbourhood is devdocs/progress/backlog/ -- a folder whose members share how they were FILED, not what they are about -- so widening deterministically sends every agent who asks to the same twelve items. Measured: frankA and frankC independently started the identical twelve-job re-verification within minutes, same jobs, same order, both correctly following the rule. Git sees nothing (no files touched) and the coordinator sees nothing (both would report the same honest topic). frankA killed theirs on noticing testmgr's own 'another testmgr shares this box' line. |
— |
| meta-dialect-extensions-and-fpc-strict | A | 5 | meta | Meta: pxx dialect extensions ⟷ FPC compatibility (two aims, switch-guarded) | — |
| task-u-evaluate-the-2026-08-31-ticket-rules-next-week | U | 60 | task | Owner asked to evaluate the new rules next week. Written as a ticket rather than a scheduled callback BECAUSE timed callbacks are one of the rules. Carries the 2026-08-31 baseline so the comparison is possible at all -- without it, next week's evaluation is an opinion. | — |
backlog-libs (28)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| bug-b-copy-cannot-compile-at-all-on-the-frozen-string-path | B | 45 | bug | ANY use of Copy refuses under -uPXX_MANAGED_STRING -- the frozen-string path the Makefile calls FROZEN_PXXFLAGS and the bench compiles with -- because lib/rtl/textfile.pas:291 calls PXXIoErrorHook, which only compiler/builtin/builtinheap.pas declares and which the frozen build does not get. s := Copy('abcdef',1,3) is enough. Not managed mode: PXX_MANAGED_STRING is ON by default, so -d is the default and -u is the affected half. Pre-existing, identical on pin v404, and invisible because the only source the frozen path is routinely given is test/hello.pas. |
— |
| bug-b-currheapused-does-not-return-to-its-prior-value-after-a-freed-block | B | 20 | bug | MEASURED AND NOT DIAGNOSED — this is the residual of an exculpation, filed so it has an owner rather than a claim. A program doing 100 IntToStr/concat iterations and NOTHING else reports Lost: 208 bytes through the FPC testsuite's own DoMem helper under pxx, and Lost: 64 bytes under fpc 3.2.2. So CurrHeapUsed is a high-water reading in BOTH compilers and neither returns to zero, which is the fact that exculpates exception handling in texception3 (all 119 sub-tests pass; only the final DoMem assertion fails). The open question is whether pxx's number SHOULD return, and it is not answered here. Also unmeasured: pxx reports Size: 262144 Kb where fpc reports 352 Kb — a 745x difference on the first line, which is the arena, and which nothing has said is wrong. |
— |
| bug-b-mkkiosk-selfhost-compares-two-stages-so-a-pinned-seed-reports-no-fixedpoint | B | 50 | bug | tools/mkkiosk.sh --selfhost decides the in-VM fixedpoint by comparing stage1 against stage2, which converges ONLY when the seed compiler already matches the sources being built. Seeded with $(PXX_STABLE) -- which is the convention this lane's own rule prescribes -- it prints NO FIXEDPOINT: stage1 != stage2 on a tree whose fixedpoint is fine. Measured 2026-09-10 at 32fb438eb: the same chain on the host converges one round later, stage2 == stage3 byte-identical (sha256 fe40bf55e1412ba5, 8140060 bytes) against stage1's 11969660. NOT a compiler defect and nothing to fix in compiler/**; the check needs a third round (or to state its seed precondition and refuse an old one). THE FAILURE MESSAGE IS THE ACTUAL COST: NO FIXEDPOINT on the project's headline property reads as a self-host regression, and it is what a reader following the docs' own recommended compiler will see. |
— |
| bug-b-terminalsize-answers-enotty-on-xtensa-and-the-probe-cannot-say-why | B+S | 20 | bug | TerminalSize returns FALSE 80x24 on xtensa under qemu-xtensa even inside a pty that every other target reads as 132x40, because PalIoctl(1, TIOCGWINSZ) answers -25 (-ENOTTY). TWO CANDIDATE CAUSES AND THIS PROBE CANNOT SEPARATE THEM: xtensa Linux may use BSD-style ioctl command encodings rather than the $5401/$5402/$5413 generic ones ansiterm hardcodes, or qemu-xtensa may simply not present fd 1 as a tty. Measured: BOTH the generic $5413 and the BSD-style $40087468 give -25 on xtensa, while on x86-64 in the same pty the first gives 0/132x40 and the second gives -25 -- so -25 is exactly what a wrong constant looks like AND what no-tty looks like. Not a regression: xtensa had no ioctl syscall number at all before 2026-09-04 and took the same 80x24 fallback. | — |
| bug-b-the-from-scratch-crypto-has-no-side-channel-claim-and-the-vectors-cannot-find-one | B | 40 | bug | The from-scratch crypto stack (aesgcm, ecdsa_p256, rsa, sha256/512, x509, tls13_* — 3,114 lines over 13 units) is validated against published SPEC TEST VECTORS by 11 test units, and makes NO claim about side channels: across the whole stack the only mention was rsa.pas's { constant-time-ish compare }, which was a length-independent compare and not constant time. THE VECTORS CANNOT FIND THIS BY CONSTRUCTION — a timing leak produces CORRECT VALUES, so every vector passes while the property is absent, which is CLAUDE.md's match-the-assertion-class-to-the-defect-class rule with a cryptographic consequence instead of a missing free. NOT AN ALARM, and prio 40 for a measured reason: tls.pas names the OpenSSL backend 'the safe default' and tls13_native does NOT self-register (a caller opts in via Tls13NativeRegister), so this code is reachable only deliberately and nothing ships it by default. The defect is that the ASSUMPTION was nowhere — a later seat could promote the native stack to default on the strength of a green vector suite and would be reading a correctness result as a security one. Headers in aesgcm.pas and ecdsa_p256.pas now state what is and is not asserted; this ticket owns the residual question. |
— |
| bug-b-val-of-a-float-is-not-correctly-rounded-while-strtofloat-of-the-same-string-is | B | 60 | bug | IT CORRUPTS OTHER PEOPLE'S MEASUREMENTS BEFORE ANYONE NOTICES IT AS A PARSER DEFECT, and that is the argument for its priority rather than the count: a 6000-pair ArcTan2 differential reported 2569 of 6000 ArcTan values disagreeing with glibc, against a file header that says ArcTan is exact, and the harness was the wrong thing -- it had Val'd the decimals into different doubles than CPython did. Val(s, d, code) for a Double parses 1574 of 6000 ordinary 17-digit decimals to the WRONG double, while StrToFloat parses the same 6000 strings correctly. Two parsers for one concept: sysutils' ParseFloatCore is correctly rounded; ValFloat in compiler/builtin/builtin.pas accumulates mant*10+d and then divides by a power of ten, so every digit rounds and the error compounds. Val is not a niche entry point -- Read/ReadLn of a float lowers to ValFloat (pasparser_stmt.inc:4239 and :8343) and PXXVarNumCoerce's string-to-number ladder calls it, so a program that reads a float from a file gets a value that is not the nearest double to what the file said. The obvious remedy does NOT apply: builtin.pas is a BUILTIN unit and cannot reach sysutils, which is the same wall math.log hit. |
— |
| bug-e-every-world-reports-meta-name-rijn-so-a-banner-cannot-name-the-scene | E | 60 | bug | ROOT CAUSE: meta.name is COPIED FROM A PARENT BUILD AND NEVER UPDATED, so a derived world inherits its ancestor's name -- and the world DIRECTORY, the only true identity, is never emitted anywhere. So a banner cannot name the scene and no measurement can be attributed from its own log. ENUMERATED, not sampled: twelve world/*/index.lzi, and meta.name is one of three values across all of them. TILE COUNT IS NOT THE DISCRIMINATOR AND THIS TICKET SAID IT WAS FOR ONE HOUR -- roofs and uv agree on name=rijn, tiles=4, pounds=1, routes=5, i.e. EVERY field a banner prints, and that 4-tile class is the one the shipping scene is in; revet-control, revet-test and skel-test are a THREE-WAY identical collision at name=wageningen, tiles=2, pounds=1, routes=5. Tile count separates the 4-tile class from the 432-tile class and does nothing WITHIN either. 7a's verdict on how both of us got there: a guard that passes the FAR case is not evidence -- it was tested against 432-tile rijn, never against the near twin, a positive control drawn from the easy end. AND THE SURVIVING DISCRIMINATOR IS ONE ROW WIDE: rijn and rijn-v9 separate only on pounds, 163 versus 162, so any scheme that identifies a world by its shape is one edit away from collapsing. FIX, two halves, root cause above both: (1) emit the world DIRECTORY, which is unique by construction and cannot drift from a copied field; (2) DONE 2026-09-22 by 7a, and better than asked -- the stamp is now region=<invocation> tiles=<n> worldindex=<sha16 of the index.lzi that actually resolved> twins=<computed set>; tile count ALONE would not have caught the case that bit, since uv is a 4-tile twin and only the index hash separates them. A guard that must name the ambiguity should COMPUTE the set it cannot exclude rather than list it (OK (class of roofs: 4 tiles; indistinguishable from: uv)), because world/ gains directories. THE PROFILE IS NOT AFFECTED: PROFILE-2026-09-21.md:29 records --region rijn as an INVOCATION and independently quotes atan2 calls/frame at 46.0 on roofs against 1,258.4 on rijn (:222), a 27x gap no 4-tile run could produce -- a relay of this put the roofs side at 6.8, which appears nowhere in the file; read off the source 2026-09-22. AND THOSE COUNTS ARE CPYTHON'S, stated at :222, so they transfer on the argument that the program logic is identical, which is an argument and not a pxx measurement. THREE DOCUMENTS ARE MISLABELLED -- FINDINGS-demo-leak.md:10 and :691, PREREG-water-sim-bisect.md:9 -- and their NUMBERS STAND (interleaved legs within one scene; a kB/s rate does not depend on which 4-tile world it was). What breaks is REPRODUCTION: --region rijn gives 432 tiles and matches nothing. The fix is a label, not a re-run. |
— |
| chore-b-no-cross-loader-on-this-host-blocks-the-dynlib-arm-run | B | 20 | chore | The dlopen loader is unverified by an actual RUN on arm32/aarch64 because this host has no cross ld-linux or cross libc — /usr/arm-linux-gnueabihf/lib and /usr/aarch64-linux-gnu/lib do not exist at all. Host provisioning, not code: no ticket resolving will make a cross libc appear. Split out of feature-real-dynlib-loader so that feature stops resurfacing at p45 with nothing actionable in it. | — |
| feature-b-classes-has-no-tcollection-family | B | 20 | feature | TCollection, TCollectionItem and TCollectionEnumerator are not declared anywhere in lib/rtl. Measured 2026-09-09 at compiler 177239049b43: Collection := TCollection.Create(TCollectionItem) fails with a statement cannot start with '.' -- the parser hitting an unknown type, NOT a for-in or a frontend problem, and the diagnostic points nowhere near the cause. THIS IS THE WHOLE REMAINING RESIDUAL OF conformance row tenumerators1.pp, which is otherwise done: TList/TFPList/TStrings/TComponent enumerators now exist with FPC's own type names and its hand-driven MoveNext/Current protocol (fixture test/lib_classes_enumerators, 25 rows byte-identical to fpc 3.2.2 under BOTH the HEAD and the PINNED compiler), so three of that file's five sections run. Prio 20 and not higher because ONE corpus row wants it and no real source in the tree does -- the search that says so is a grep of lib/, examples/ and test/ for the name, which finds nothing outside the skip list. Rank it up the moment a corpus rung or a real program asks; TCollection is how FPC code models an owned, indexed, notifying child list (TFont.Collection, dataset field defs, and every component-editor idiom), so that ask is likely rather than hypothetical. |
— |
| feature-b-delphi-extended-rtti-object-model | B | 30→40 | feature | FILED 2026-09-06 to give [[feature-embed-dwscript-rtti]] a named blocker instead of a dead end -- that ticket was ranked at 40 for work nobody could start, and 'needs extended RTTI' appeared in no ticket anywhere (checked: only the DWScript ticket and [[feature-p-resolve-delphi-dotted-unit-scope-names]] mention TRttiContext at all). lib/rtl/rtti.pas is 294 lines and exports TRttiMethod and TRttiProc; there is no TRttiContext and TRttiContext.Create does not parse. Delphi's model is the reflective object graph -- TRttiContext -> TRttiType -> TRttiMethod/TRttiProperty/TRttiField/TRttiParameter -- walked at runtime, which is a DIFFERENT shape from the classic typinfo GetPropInfo/GetStrProp accessors that lib/rtl/typinfo.pas already implements to FPC parity (16 by-name arms landed 2026-09-05, differentially verified byte-for-byte against FPC's own typinfo). NOT FPC PARITY, AND THAT IS THE POINT: fpc 3.2.2 has TRttiContext and TRttiType but NOT TRttiIndexedProperty, so our usual oracle cannot settle the surface and cannot be diffed against -- this is reach into the Delphi half of the corpus, the same category as the unitalias work. SIZE IS UNSCOPED ON PURPOSE: nobody has costed which subset a real consumer needs, and dwsRTTIExposer alone wants 15 distinct TRtti* classes. Whoever takes it should scope from ONE consumer rather than from Delphi's documentation. |
— |
| feature-b-erroraddr-is-missing-from-system | B | 40 | feature | FPC's System unit exposes the writable global ErrorAddr: Pointer (the address a runtime error was raised at, cleared by a handler that recovers). pxx has no such symbol: erroraddr := nil gives undefined variable (erroraddr). Measured 2026-09-05 at 36d7e5fd4, compiler e6af001d6c0e3bf2. It is the FIRST error in erroru.pp, a helper unit that FIVE conformance skip rows use -- tobject1, tstring2, tstring4, tstring5, texception3 -- so it is one of exactly three symbols standing between those rows and a compile; the other two are TFPCHeapStatus and GetFPCHeapStatus, which are feature-b-getfpcheapstatus-needs-always-on-heap-accounting and are the harder half. Unlike that one this is probably cheap: a global plus whatever the runtime-error path already knows about where it faulted. NOT YET ESTABLISHED and the reason this is a ticket rather than a fix: whether pxx's error path HAS a raise address to publish, and whether declaring the global without populating it truthfully would be the stub CLAUDE.md refuses -- a caller that prints ErrorAddr after a recovered error would print nil with no diagnostic. |
— |
| feature-b-getfpcheapstatus-needs-always-on-heap-accounting | B | 50 | feature | FPC's System exposes TFPCHeapStatus (a record of MaxHeapSize/MaxHeapUsed/CurrHeapSize/CurrHeapUsed/CurrHeapFree) and GetFPCHeapStatus. cclasses.pas:676 uses both in its tmemdebug helper, and that is now the ONLY open wall on the FPC compiler-source corpus -- it blocks cclasses, comphook, finput and cfileutl, measured 2026-09-05 with compiler 108f95a7f278 under --mimic-fpc-compiler. The type is trivial; the FUNCTION is not, and that is the whole ticket. Our allocator has NO always-on counters: -dPXX_ALLOC_CENSUS instruments PXXAlloc/PXXFree at COMPILE time, so a released binary carries no heap accounting at all. Returning zeros would make four units compile while the function lies -- a caller printing a memory delta would print 0 with no error -- which is the compiler-appeasement workaround CLAUDE.md refuses. The real work is deciding whether the allocator carries always-on counters and paying that cost per allocation. |
— |
| feature-b-posix-and-fpc-named-socket-facades | B | 25 | feature | BLOCKED on decide-posix-master-vs-fpc-named-master-for-the-socket-facades: the design says Posix.* is canonical and the FPC-named units wrap it, but the tree shipped the FPC-named units AS the implementation on PAL, and all three of the design's selectable backends already exist one layer down at the PAL. Building as designed would invert a working layer with 15 in-tree consumers plus Synapse, for zero current consumer. Not implementation work until the layering question is re-decided. | decide-posix-master-vs-fpc-named-master-for-the-socket-facades |
| feature-b-the-rtlevent-family-is-absent-from-the-threading-rtl | B | 35 | feature | The RTLEvent family is absent from the threading RTL | — |
| feature-b-threading-condition-is-absent-and-the-corpus-that-justified-omitting-it-has-moved | B | 50 | feature | threading.Condition is not implemented and lib/rtl/mimic_threading.pas:55 says so in its own words, in a ## What is NOT here list alongside Semaphore, Barrier, local, current_thread, active_count, Timer and Thread subclassing. THE OMISSION WAS REASONED AND THE REASON HAS EXPIRED: that paragraph justifies itself with "None is used by the corpus this was measured against, and each would be a claim with no test behind it" -- which was a good reason and is now false for exactly one member of the list. TSP uses Condition at TWO sites, tsp/voice.py:79 and :158, and needs THREE pieces of surface, not one: construction, notify() (:96, :169) and wait() (:131, :191), plus the CONTEXT-MANAGER protocol, since every use is with self._cv:. A Condition that is constructible but not a context manager fails these call sites as completely as no Condition at all. REPORTED BY frankz-e5 off the TSP wall survey as one site behind at least three board rows; re-verified here in TSP's source rather than on report, which is how the second site and the with requirement turned up. RECORDED NOWHERE REACHABLE UNTIL NOW -- it was mentioned only inside a CLOSED wave ticket, which is the least likely place in the tree for anyone to look, and that is why it is a ticket rather than a logbook line. INDEPENDENT OF THE OPEN DECIDE, and say so when working it: decide-should-a-python-program-that-imports-threading-compile-as-written (p55) asks whether import threading should stop being a hard refusal without --threadsafe. This gap survives EITHER answer -- a program that passes --threadsafe today still fails on Condition -- so this ticket must not be worked in a way that pre-empts that decision, and must not be treated as blocked by it either. WHAT RETIRES THIS: Condition constructible, usable as a context manager, with wait/notify, and tsp/voice.py compiling past both sites. The rest of the What is NOT here list stays out of scope on its own stated reasoning until a named consumer appears for it too -- the point here is the expired premise for ONE member, not that the list is wrong. |
— |
| feature-demo-nilpy-ide | B+E | 30 | feature | Landmark demo: a minimal IDE in Nil-Python via import tk — max functionality, minimal code | — |
| feature-demo-portable-userland | B+E | 35 | feature | PXX portable userland (mini OS-personality) — one shell, any kernel | — |
| feature-demo-songformatter-pxx-target | E | 68 | feature | songformatter as a pxx compile target (nilpy) — GUI editor + live preview | bug-nilpy-a-python-override-of-a-virtual-pascal-method-segfaults-when-called-back-from-the-pascal-side |
| feature-dns-esp-wire-nameservers-from-lwip | B+S | 15 | feature | Half 2 of the feature-dns-esp-backend split: where dns_wire gets its nameservers on ESP. Only matters for the explicit opt-in case -- someone who wants PXX's own resolver instead of lwIP's -- because the default route now goes through lwIP's getaddrinfo and never reads a nameserver list. dns_getserver is in liblwip.a for it; its ip_addr_t return wants a small C shim rather than hand-computed offsets. | — |
| feature-embed-dwscript-core | B | 40 | feature | Rung 4 of [[feature-pascal-corpus-expansion]], split out of [[feature-embed-dwscript-rtti]] on 2026-09-06 because the two halves have DIFFERENT STARTABILITY: this one is startable today and the exposer is not. THE lclintf WALL IS NOT A LAZARUS DEPENDENCY -- measured at 8b55d1918, compiler 5ca36ce7aae9: dropping an EMPTY lclintf.pas into Source/ makes the compile walk straight past it, so LCLIntf's own surface is used ZERO times (grep -o 'LCLIntf\\.' finds no qualified call either). It is load-bearing only for what it TRANSITIVELY RE-EXPORTS: dwsXPlatform imports System.SyncObjs in its non-FPC arm only (line 71) while deriving TdwsCriticalSection from TCriticalSection unconditionally (line 99), so under --mimic-fpc the type has to arrive through LCLIntf. A stub re-exporting our own syncobjs -- which HAS a real TCriticalSection -- clears it and the wall moves to lines 141/163. WHAT IS ACTUALLY LEFT IS AN ORDINARY RTL GAP LADDER, not a port: TFileName, TLightweightMREW and IMultiReadSingleWrite are absent from lib/rtl (all three checked), and TFileName is a one-line sysutils alias. Prerequisite LANDED: [[feature-p-resolve-delphi-dotted-unit-scope-names]] is in done/, and Source/pxxlib.cfg already carries the 11 unitalias rows. INVOCATION TRAP THAT COST THIS MEASUREMENT AN HOUR: the manifest is found by walking UP from the unit's own directory and the walk STOPS BEFORE THE CWD, so cd Source && pxx p.pas silently gets NO manifest while pxx -FuSource p.pas from the parent gets one -- same tree, same files, different answer, no diagnostic. Filed as [[bug-p-a-manifest-is-skipped-in-silence-when-the-source-is-compiled-from-its-own-directory]]. Corpus is 96 .pas in Source/ (128 including subdirs), NOT the 102 the parent ticket claimed. Nothing vendored; MPL 1.1 attribution obligations are on the parent ticket. |
— |
| feature-embed-dwscript-rtti | B | 40 | feature | SPLIT 2026-09-06 AND THIS TICKET IS NOW THE EXPOSER HALF ONLY. The core went to [[feature-embed-dwscript-core]] (corpus rung 4, startable today) and the blocker this half waits on is now a filed row, [[feature-b-delphi-extended-rtti-object-model]], instead of a sentence -- the ranker had been seeing prio 40 with blocked-by [] for work nobody could begin. PREMISE STILL FALSE AND STILL THE HEADLINE: dwsRTTIExposer does not use typinfo. It uses Delphi EXTENDED RTTI -- 15 distinct TRtti* classes including TRttiIndexedProperty eight times -- and fpc 3.2.2 CANNOT COMPILE IT EITHER (it has TRttiContext/TRttiType, not TRttiIndexedProperty), so our usual oracle settles nothing here. pxx lib/rtl/rtti.pas exports TRttiMethod and TRttiProc and has no TRttiContext at all. TWO CLAIMS THAT WERE HERE ARE NOW CORRECTED. (1) This summary said the ONLY remaining wall was Delphi dotted unit-scope names; that feature LANDED -- [[feature-p-resolve-delphi-dotted-unit-scope-names]] is in done/ and Source/pxxlib.cfg carries 11 unitalias rows. (2) Its replacement, recorded in that done ticket, was that the wall moves to lclintf and DWScripts FPC branch WANTS LAZARUS. Measured false at 8b55d1918 / compiler 5ca36ce7aae9: an EMPTY lclintf.pas makes the compile walk straight past it, LCLIntf is never qualified anywhere in dwsXPlatform, and what it was load-bearing for is a transitive re-export of TCriticalSection -- our own syncobjs has a real one. What remains is an ordinary RTL gap ladder (TFileName, TLightweightMREW, IMultiReadSingleWrite), all recorded on the core ticket. THAT CORRECTION ALSO FIXES A CITATION: the done ticket says the Lazarus problem was Recorded on [[feature-embed-dwscript-rtti]] and it never was -- the sentence read as a receipt for a write that did not happen. Corpus is 96 .pas in Source/ (128 including subdirs), not the 102 claimed here before. The typinfo work recorded below was real FPC parity, is differentially verified, serves dwsComp.pas, and moves this ticket zero lines. Nothing vendored; MPL 1.1 obligations below still apply to any demo. |
feature-b-delphi-extended-rtti-object-model |
| feature-embed-pascal-script | B | 45 | feature | RE-MEASURED 2026-09-06 at d4fe6ede3 / compiler e7d85ae887d9 -- premise CURRENT, and THREE of the four recorded walls are now down. uPSUtils still compiles CLEAN. (1) missing PByteArray -- fixed. (2) a value cast to a string alias dropped a following INDEX -- fixed 9339d6661. (3) THE STRING-ALIAS-CAST-OVER-A-POINTER-SLOT WALL IS FIXED and this summary said otherwise: [[bug-p-a-string-alias-cast-over-a-pointer-slot-is-a-no-op-and-reads-the-pointer]] is in done/, and verified by RUNNING it rather than by reading the folder -- t(p) := 'abc'; writeln(t(p)) now prints abc (was 4261104), Length answers 3 (was the pointer), SetLength answers ab (was a hard refusal), all four rows byte-identical to fpc 3.2.2. (4) A NEW WALL WAS FOUND AND FIXED IN THE SAME SESSION: uPSCompiler catches EZeroDivide and stopped at on: unknown exception class, because pxx had NO float exception family at all -- EInvalidOp descended from Exception, EZeroDivide/EOverflow/EUnderflow did not exist, and three FLOAT runtime errors raised INTEGER classes, so on E: EMathError caught nothing. Fixed in lib/rtl/sysutils.pas at d4fe6ede3, all 7 hierarchy rows and 10 test lines identical to fpc, guarded by test/lib_math_exception_tree.pas. uPSCompiler's wall has therefore moved 2753 -> 3776 -> 5031, and the CURRENT one is @Func.Attributes.Items[i].AType.OnApplyAttributeToProc: @ over a CLASS base consumes only ONE selector, filed as [[bug-p-at-over-a-class-base-consumes-only-one-selector]] and now this ticket's blocked-by. AND THE uPSRuntime CLAIM WAS STALE TOO: this summary said uPSRuntime stops earlier on a {$IF} comparison. With --mimic-fpc that wall is gone -- it now parses to line 3049 and stops on function read(var Data; Len: Cardinal): Boolean, a nested helper, because read/write/readln/exit/halt cannot be DECLARED as user routines here while fpc accepts all five (writeln we refuse and so does fpc -- that row is parity). Filed as [[bug-p-read-write-exit-and-halt-cannot-be-declared-as-user-routines]]. So the two remaining walls are both compiler-lane, both located, both with reduced repros, and NEITHER is a library gap. NOT vendored -- probed against a shallow clone outside the repo (44 units in Source/). |
bug-p-a-call-through-an-indexed-property-in-the-chain-does-not-resolve |
| feature-parallel-load-sampler-refine | B | 20 | feature | Parallel load sampler — refinements (ramp/EMA, BSD/cgroup) | feature-os-targets-bsd-mac |
| feature-pcl-cross-platform-gui | B | 30 | feature | UMBRELLA: cross-platform GUI — copy the LCL widgetset model; PCL = TComponent tree behind a TWidgetSet seam; compile-time widgetset select; sparse widgetset×OS matrix, hard-fail the rest | feature-pcl-seam-seal, feature-pcl-widgetset-select, feature-pcl-win32-widgetset |
| feature-random-esp-hw-tier | B+S | 40 | feature | The ESP arm of feature-random-library, split out so the parent stays claimable for its four buildable targets: the ESP32 HW RNG register as tier 1, and Randomize's seeding on a bare boot that has no clock. Split proposed by the coordinator on the correct ground that the ranker's blocked-by has no notion of PARTIAL — but the blocker that motivated the split does not reproduce here, so this ships with no edge and a stated measurement to settle it. | bug-a-the-no-fpu-diagnostic-advises-uses-softfloat-which-does-not-help |
| perf-b-the-inverse-trig-functions-have-no-fast-arm-and-cost-16-microseconds | B | 45 | perf | RESOLVED for ArcTan and ArcTan2 (FastAtanD, the fdlibm kernel, behind the existing {$else} arm); ArcSin and ArcCos DELIBERATELY LEFT on the double-double path and that is the part a future reader must not undo casually. MECHANISM, unchanged: a fast arm exists for Sin/Cos/Tan behind {$ifdef PXX_FLOAT_EXACT} and the inverse family had none, so every ArcTan2 paid ~106 bits for a 53-bit answer. MEASURED 2026-09-22 on pinned v416 fddc21e7e6615f80, both arms built with that binary, min-of-5 net of a measured loop control, box at load 27-30: dd 29463 ns/call vs fast 222 ns/call, 133x net (85x gross). CARRY BOTH ROWS: this ticket previously recorded 14396 ns/call for the same function on an unstated box and input distribution; the two are not reconciled and neither refutes the other. ACCURACY: max 1 ulp against glibc over ~15600 rows spanning all four reduction brackets, 0 rows past the 2-ulp contract test/lib_math_fast_tolerance.pas asserts; the exact arm is untouched and test/lib_math_correctly_rounded.pas passes unchanged under -dPXX_FLOAT_EXACT. WHY ArcSin/ArcCos ARE NOT DONE AND WHAT WOULD CHANGE THAT: on the SHIPPING scene roofs asin is 3.5 calls/frame and acos ~0; the 0.2/frame and 24-calls-in-150s figures this summary carried until 2026-09-22 are the OTHER column, rijn, and I attached them to roofs by collapsing 7a's two-column table into prose (corrected against lekkerzeilen devdocs/perf/PROFILE-2026-09-21.md:223). THE TWO FUNCTIONS MOVE IN OPPOSITE DIRECTIONS BETWEEN THE SCENES AND 3.5-ON-ROOFS IS NOT A TRANSCRIPTION ERROR: atan2 is 27x heavier on rijn (1258.4 vs 46.0) while asin is 17x heavier on roofs (3.5 vs 0.2), so a reader who has internalised 'rijn is the dense scene' will want to swap 3.5 back and must not. The document fixes column order at the atan2 row and every later row inherits it silently, so a reader who joins at row two has no cue at all. The decision is UNCHANGED and the corrected numbers still support it: 3.5 calls/frame at the measured dd cost (~29.5 us) is ~0.1 ms, ~0.02% of a 624 ms frame. And the plain-double asin/acos identity historically measured up to 8 ulp (acos up to 1099 ulp when taken as pi/2 - asin) -- all of the accuracy risk and none of the win. It SPRINGS if a program calls asin or acos in bulk; the fix would then need its own kernel, not the atan identity in plain double. KNOWN RED, STANDING AND ATTRIBUTED TO ME: the fast arm moves 20 of 272 rows in test/test_nilpy_math_atan_and_atan2_bit_for_bit.npy by EXACTLY 1 ulp (histogram {1:20}, zero rows worse), reproduced on plexus so not environmental. The test file's own stated purpose still passes -- no NaN anywhere, every large-argument row gives pi/2, every raw-BYTES struct row agrees -- so the Dekker-split overflow it was built to pin is still pinned. The contract question is with frankuser and the owner; I have NOT touched the test and will not, because narrowing it is a loosening AND it is the change that makes my own work pass. THIS IS NOT A FRAME-RATE LEVER AND MUST NOT BE RANKED AS ONE: at 46 atan2/frame on the shipping scene roofs it is 1.36 ms, which is 0.14% of a ~1 s frame. | — |
| task-b-five-system-names-still-in-sysutils-are-waiting-on-a-pin-not-on-a-decision | B | 30 | task | ELEVEN names, not five, and the number changed because the SIX THAT MOVED CAME BACK as a deliberate duplicate on 2026-09-11. LowerCase, StrLen, StrPas, SysBackTraceStr and StringOfChar are still declared only in lib/rtl/sysutils.pas; AllocMem, DynArraySize, SetString, sLineBreak, UTF8Decode and UTF8Encode are now declared in BOTH sysutils.pas and compiler/builtin/builtin.pas. Nothing here is undecided: lib/rtl and every external corpus build with $(PXX_STABLE) against a FROZEN copy of compiler/builtin, so a name that lives only in builtin/ is invisible to them. Measured three times -- undefined variable (LowerCase) from inside sysutils.pas, undefined variable (StringOfChar) at lib_strpchar.pas:49, and on seven undefined variable (SetString) in external/synapse/synautil.pas plus undefined variable (UTF8Encode) in testjsondata.pp, which took out four tstate rows for two days. THE TRIGGER IS A PIN whose stable_linux_amd64/default/builtin/builtin.pas carries these names; grep it there, that is the whole test. Then move the five and DELETE the six duplicates from sysutils.pas. Do NOT start before that pin exists. Error is a twelfth name and is NOT part of this row: it is compiler-internal here and needs sysutils' exception hierarchy first. |
— |
| task-b-four-fpc-build-artefacts-are-committed-under-lib-asmcore | B | 20 | task | lib/asmcore/asmcore_base.{o,ppu} and lib/asmcore/asmcore_x64.{o,ppu} are TRACKED IN GIT -- four FPC build artefacts committed at 3d3ed9ab3, in a directory compiler/compiler.pas uses and make bootstrap compiles with fpc. They are INERT TODAY and that is measured, not assumed: fpc records the source timestamp inside a ppu and rebuilds on any mismatch in either direction, so any checkout makes the .pas disagree with the recorded time and the unit is recompiled. What they are is two committed .ppu in a source tree that nobody knows are there, in the one directory where an fpc-side compile happens. The live version of the hazard is an OPTION change, which the source-time check cannot see: a ppu built with -dFOO is silently reused by a compile without it. Remove them and add the extensions to .gitignore; verify with make bootstrap plus the FPC seed canary, which is the consumer that would notice. |
— |
backlog-cfront (10)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| bug-c-long-double-is-8-bytes-in-pxx-and-16-in-gcc | C | 35 | bug | C long double is mapped to double (clexer.inc:342), so it is 8 bytes where gcc's is 16. MEASURED both sides: struct { long double x; } is sizeof 16 under gcc and 8 under pxx. Any such struct crossing a real C boundary therefore disagrees about its own SIZE before any calling-convention question is reached, and psABI puts an x87 member in MEMORY class where pxx would see one SSE eightbyte. Found by writing the NEGATIVE control for the new SysV classifier: the classifier's tyExtended refusal is unreachable from C because the frontend erases the distinction first, so a guard that looks like it covers long double cannot fire. Pre-existing and independent of the aggregate-classification work. |
— |
| bug-c-sqlite-with-threadsafe-stops-at-a-stray-BEGIN_DECLS | C | 30 | bug | BARE OBSERVATION, CAUSE UNKNOWN AND DELIBERATELY NOT GUESSED. ./compiler/pascal26 --threadsafe library_candidates/sqlite/sqlite3.c stops with stray token at top level (not a declaration): '__BEGIN_DECLS', rc=1. PRE-EXISTING, not caused by ec1a1d7b6 -- verified by stashing that change, rebuilding, and getting the identical failure, so is this mine' is already answered and nobody needs to repeat the rebuild. NOT REDUCED: --threadsafe' alone builds and runs a trivial program, and #include <pthread.h>' plus --threadsafe' builds too, so the minimal repro is still the whole amalgamation. `__BEGIN_DECLS' does not appear anywhere in lib/crtl/include; it is a glibc sys/cdefs.h macro and /usr/include/pthread.h uses it once -- which is an observation about where the token lives, NOT a claim about how it was reached. The amalgamation is otherwise healthy: --emit-obj -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION compiles it clean at 4458 procs. |
— |
| bug-c-thread-local-storage-still-shares-one-copy-off-x86-64-and-a-warning-is-all-that-stands-there | C | 40 | bug | The RESIDUAL left by the __thread fix: on x86-64 with a file-scope scalar, __thread gets real per-thread storage; everywhere else — any other target, an array, --emit-obj/--shared, and FUNCTION SCOPE — it still compiles to ONE SHARED object and only a warning says so, and AS OF 2026-09-19 function scope warns too (TLSREFUSE_FUNCSCOPE, a sixth reason that is not a refusal but a declaration that never reached the allocator) — so the family no longer has a silent member, which was this ticket's sharpest row. The SHARING is untouched at every one of the five: the warning deliberately does not reach TryAssignThreadVarStorage, because a new class of consumer would meet an area size baked before anything is lexed and a full area is now a hard Error, so wiring it would stop a program compiling that compiles today. Real storage for function scope is feature-c-a-function-scope-thread-local-gets-real-per-thread-storage, blocked on the sizing move. A SECOND DEFECT AT THE SAME SEAM was measured while fixing this and is NOT fixed: a BARE __thread in a body, no static, returns a garbage stack local where gcc refuses the program — bug-c-a-bare-thread-in-a-function-body-is-accepted-and-returns-a-garbage-value, ranked ABOVE this one because it is wrong on one thread today. Not a regression; this is byte-identical to the behaviour before the fix. NOTE the function-scope row is about SHARING ONLY: the separate bug that made it a wrong value single-threaded (the static being dropped) is fixed and is bug-c-a-block-scope-static-is-silently-dropped-when-a-thread-storage-class-precedes-the-type. |
— |
| feature-c-a-function-scope-thread-local-gets-real-per-thread-storage | C | 40 | feature | static __thread int t; inside a function body now WARNS that it gets one shared copy (TLSREFUSE_FUNCSCOPE, 2026-09-19) — this ticket is the other half: giving it real per-thread storage, as file-scope scalars get on x86-64. IT IS BLOCKED ON THE AREA SIZING AND THAT IS THE WHOLE REASON IT IS NOT DONE ALREADY: the thread-local user area is sized by EmitTlsMainInstall BEFORE anything is lexed, so a NEW CLASS OF CONSUMER meets a size chosen without knowledge of it, and since 402d61e0d a full area is a hard Error rather than a warning. Wiring function scope in today would therefore make a program with several block-scope thread-locals STOP COMPILING where it compiles now (silently wrong under threads) — an acceptance regression in the direction 402d61e0d's author deliberately chose for FILE-scope declarations, where the programmer wrote __thread and can read the flag in the diagnostic; a block-scope static in a vendored dependency is not that case. The conjunction that produced eight red rows on 2026-09-19 (a zero-sized NilPy area, errno becoming __thread, area-full becoming an Error) is the same mechanism firing, so the sizing is known-moving rather than merely suspected. ir_codegen.inc's own comment names the prerequisite in its own words: "a cleverer scan is not the improvement here; moving the decision is, and that is a different change with its own measurement". The warning is landed and correct meanwhile; nothing here is urgent and nothing is silent. |
feature-a-the-threadvar-area-is-3072-bytes-of-bss-in-every-program-that-has-no-threadvar |
| feature-c-crtl-resolv-h-and-the-ns-parser | C | 40 | feature | networking/nslookup.c is the last busybox translation unit stopped by a header that is really an implementation. It needs struct __res_state and the res global, res_init/res_mkquery/res_msend, and the ns* message-parsing API -- ns_initparse, ns_parserr, ns_msg/ns_rr and their accessors, ns_name_uncompress. One TU, so it ranks below regex.h (7); filed separately because the two share nothing but their shape. |
— |
| feature-c-csmith-differential-fuzzing | C | 40 | feature | C differential fuzzing (csmith vs gcc) — campaign, PAUSED with the harness live | — |
| feature-c-esp-conformance-coverage | C+S | 18 | feature | C conformance / feature coverage on ESP (xtensa + ESP32-C3 riscv32 bare) | — |
| feature-c-package-namespace-decision | C | 35 | feature | Decide the Pascal-import namespace for C packages (uses zlib collision) |
— |
| idea-c-realworld-test-targets | C | 60 | idea | Real-world C programs as compiler stress tests (brainstorm) | — |
| perf-c-parse-codegen-large-file-superlinear | C | 25 | perf | perf: C parse+codegen shows mild superlinear scaling on very large amalgamations | — |
backlog-web (8)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| bug-w-status-benchmarks-503s-while-every-sibling-page-serves | W | 40 | bug | https://pxxc.org/status/benchmarks/ has answered 503 for hours while /, /status/ and /status/tests/ all serve 200 — so it is one deployed page, not the site. It was verified working on 2026-08-30 with its content marker [fib sieve] by task-d-verify-the-published-status-urls, which makes this a regression against a checked baseline rather than a link that was never right. docs/reference/status.md:16 cites it correctly and DELEGATES its numbers to it, so a reader is told where the timings are and gets a 503. Docs deliberately unchanged: removing the link would turn Track D's gate green by deleting the only thing pointing at the outage. |
— |
| chore-web-secrets-sops-age | W | 45 | chore | Website secrets: SOPS + age, encrypted-in-git, paper-backed key | feature-web-track-w-bootstrap |
| feature-promo-launch-plan | W | 15 | feature | Promo & launch plan — visibility now, 0.1 beta next, the loud moment last | — |
| feature-web-blog-bootstrap | W | 35 | feature | /blog/ returns 200 and says Coming soon. [[feature-promo-launch-plan]] already decided that VISIBILITY starts now and is ungated — the blog is the surface that decision needs and it does not exist yet. This ticket is the MACHINERY plus two concrete first posts; the strategy, the audience and the one-shot launch guard all live in that ticket and are not relitigated here. |
— |
| feature-web-machine-readable-project-metadata | W | 40 | feature | pxxc.org serves no /llms.txt (404) and no JSON-LD structured data. The site is otherwise unusually legible to machines — server-rendered, indexed, and summarised ACCURATELY including the byte-identical discipline holding under compression — so these two files are the remaining gap in a channel that already works, not a rescue job. |
— |
| feature-web-syndication-feeds | W | 30 | feature | The site publishes two things that change continuously — the Latest resolved ticket list and (once it exists) the blog — and offers no RSS/Atom feed for either. No application/rss+xml or application/atom+xml link anywhere in the head. A follower has no way to follow, and the one genuinely novel asset (a live public record of a compiler being built by an agent fleet) is unsubscribable. |
— |
| feature-web-track-w-bootstrap | W | 40→45 | feature | Track W (website) — bootstrap the lane: two repos, one board | — |
| feature-web-tracker-and-host-portability | W | 45 | feature | Public tracker on GitHub + host-portability rule (nothing lives only in a service) | feature-web-track-w-bootstrap |
backlog-windows (4)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| feature-pcl-tk-windows-compat | M | 25 | feature | NilPy tk on Windows — quarantine the Tcl/Tk-DLL-swarm problem behind a {$ifdef WINDOWS} include in tk.pas; emulate/wrap, stub now fill later. Linux keeps the real embed | feature-port-windows-pe |
| feature-pcl-win32-widgetset | M | 25→30 | feature | PCL: native Win32 widgetset — a 2nd TWidgetSet subclass over user32/gdi32, zero-dep (no GTK bundle). Best-effort, UN-GATED (no Windows box, Wine-smoke only) | feature-pcl-seam-seal, feature-port-windows-pe |
| feature-port-windows-pe | M | 25→55 | feature | Windows/x64 target — PE/COFF writer, MS x64 ABI, IAT imports; testable via Wine | feature-port-rtl-over-libc |
| feature-t-windows-wine-harness | M | 20 | feature | Windows/Wine test bed — scratch-prefix wine runner + mingw-w64 differential oracle, hello-world gate | — |
backlog-docs (1)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| bug-d-claude-md-still-prescribes-a-touch-the-stamp-fix-made-unnecessary | D | 45 | bug | CLAUDE.md's per-fix-loop section tells readers to touch the sources after seeding a tree from outside, because a copied-in binary's mtime made make compiler/pascal26 a no-op that exits 0. The $(COMPILER_STAMP) mechanism closed that hole; measured 2026-08-30, a cp'd seed newer than every source still builds and converges. The instruction is now cargo, and it sits in the one section that is the single source of truth for gating. |
— |
backlog-esp (5)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| bug-s-a-direct-call-to-an-interrupt-routine-is-the-same-trap-return-fault-by-another-spelling | S | 35 | bug | The SIBLING SPELLING of the refusal landed in d305e1afa, split out rather than closed with it because refusing it needs a design decision and not a predicate. A routine declared interrupt; returns via a trap-return (riscv32 mret, xtensa rfe), so ANY normal entry into it is a fault -- and @f is not the only normal entry. A DIRECT CALL, MyIsr;, is the same fault and is still accepted with no diagnostic. It is ranked well below its @proc sibling on likelihood rather than on severity: the @proc form is the natural esp_intr_alloc idiom and a reasonable person writes it by accident, whereas calling an ISR by name is visibly odd. THE OBSTACLE IS NOT THE PREDICATE, WHICH IS ONE LINE BESIDE THE EXISTING ONE -- it is that test/test_esp_interrupt.pas RELIES on a direct call (behind a runtime-false guard, if counter < 0 then MyIsr;) as its only way to force the handler body to be emitted, and a structural probe needs some way to do that. So refusing this arm requires an emission-forcing mechanism first: a {$KEEP}-style directive, an export, a reference that is not a call, or making interrupt; bodies unconditionally retained by DCE. That choice is the ticket. Whoever takes it should decide the mechanism before writing the refusal, because a refusal landed without one breaks the only fixture that proves the ISR codegen is emitted at all. |
— |
| bug-s-c-on-the-esp-profile-cannot-reach-crtl | S | 45 | bug | A C source that reaches crtl does not build on the ESP profile: #include <stdio.h> plus a printf stops with compiler error: PXXMemZero not found under --target=xtensa --emit-obj, on BOTH the default profile and --esp-profile=bare. The 2x2 says the discriminator is the PROFILE, not the output mode -- the same source builds with --platform=posix as an executable AND as an object. PXXMemZero is defined unconditionally in compiler/builtin/builtinheap.pas:4561 (only its fast paths are CPUX86_64-guarded), so the symbol EXISTS and the lookup is not reaching it: the builtin heap unit is not being pulled into a C compilation on PLATFORM_ESP. Bounds decide-should-a-c-main-exist-on-the-esp-profile-at-all, which established that --emit-obj is the shipping path for C here -- true for FREESTANDING C and not yet for C that calls into crtl. |
— |
| bug-s-install-esp32-target-names-a-package-that-is-virtual-only-on-26-04 | S | 25 | bug | tools/install_esp32_target.sh:96 asks for qemu-user-static, which on 26.04/resolute survives as a PURE VIRTUAL package: three instruments say it exists and only apt-cache policy says it cannot be installed. The script's own apt_has_candidate() already does the correct Candidate: test, so it WARNS rather than dying and blocks nobody today -- but it will not install the renamed package on a fresh 26.04 box. Not urgent; filed so the rename lands with the measurement rather than being rediscovered. The real package is qemu-user-binfmt. |
— |
| feature-esp-hardware-flash-validation | S | 25 | feature | HARDWARE-GATED AND NOT WORK ANYONE CAN PICK UP TODAY -- the prio is 25 for that reason and NOT because the ticket is unimportant. It is the row that decides whether the ESP work pays: two of the fleet's open technical claims live here and neither can be closed without a board on USB. (1) UART: tools/esp_flash.sh gained a --project pass/hang verdict (7d4f7ea33) and all four NilPy demos are OK in qemu against pin v413 -- but the log filter was written against QEMU output, so a filter tuned to qemu can strip a line a physical part prints and report a clean pass, which is this ticket's own first acceptance row failing silently. (2) ISR: the acceptance asks that a peripheral/ISR FIRE, and firing is necessary and not sufficient -- boxing ALLOCATES, an allocation inside an interrupt handler faults later on another context's heap, and every timing number in between is correct because the timing IS correct. A run printing tick=1..5 status=0 satisfies the row as written and says nothing about the contract; what settles it is an assertion on the ALLOCATOR (allocation count unchanged across N ticks), a different assertion class from expect_same. THE CONDITION THAT SHOULD MOVE THIS PRIO is a board existing on the box -- the owner said 2026-09-20 that ESP32 is priority and that he will try to set hardware up later; until then a high rank would send seats to work they cannot start. Raise it the day silicon arrives, not before. WHY VISIBLE-BUT-LOW BEATS HIDDEN, and this is the argument that survives someone disagreeing about how likely a misdispatch is: THE TWO FAILURES HAVE DIFFERENT HALF-LIVES. Unpickability is short-lived and SELF-RESOLVING -- the moment a board exists the objection evaporates on its own. Invisibility is not: a ticket nobody can see stays unseen after the condition lifts, and the lifting event produces no notification. So the asymmetry favours ranked-and-low even if the misdispatch risk were higher than it is. AND NOT blocked/: that folder's convention is ticket-to-ticket via blocked-by: (sampled 3 of 3), and "no board exists on this box" names no ticket; rainy-day/ fits the deferral but is unranked and unscanned, which trades a small failure for the larger one. frankS holds both claims and can close neither. |
— |
| feature-s-the-64-kib-esp-heap-arena-is-reserved-even-when-dce-proves-the-allocator-unreachable | S | 60 | feature | UNBLOCKED 2026-09-21 -- the PXXDynSetLen orphan is deleted (2c59f8326), so the predicate below is now decidable and testable. EspArena is 65,536 B of unconditional BSS under {$ifdef PXX_ESP} -- 97.1% of an EMPTY bare program's 67,464 B of SRAM, and ~23% of a 276,832 B free DRAM pool (THAT DENOMINATOR IS NOT MINE -- reported by frankz-e5 as the figure after f028632c3 removed the dead 64 KiB NilPy arena; I did not measure it, so the percentage is only as good as it is) -- and it is reserved even when DCE has PROVED that nothing can reach it. This is SRAM, the resource the owner ruled relevant on 2026-09-20 ('SRAM here is most relevant, ESP's have plenty flash memory so that's a lesser issue'), and it is the SECOND unconditional BSS reservation in four days to be the largest single SRAM item -- bug-a-the-signal-alt-stack-is-32768-bytes-of-unconditional-bss was 32,768 B and was fixed on 2026-09-18 by reserving iff a handler can exist. THE SAME ONE-LINE PREDICATE APPLIES AND IS NOW DECIDABLE, which is the point of filing it: measured 2026-09-21 on a de-duplicated build, an empty bare program drops EVERY allocator entry point -- PXXAlloc, PXXRealloc, PXXStrAllocSize, PXXObjAlloc, PXXObjAllocRaw, PXXObjAllocRaw2 all report <- DROPPED under --dce-why -- leaving live bodies 2 (150 B) and code 848 B, while bss stays 66,808 B. An arena with no surviving reader is reserved anyway. BLOCKED-BY the PXXDynSetLen collision, and that ordering is load-bearing rather than bookkeeping: while the orphan exists it roots PXXAlloc unconditionally, so the predicate can never come out false and a guard written on it would be untestable. The expected win is bounded and honest -- it is 64 KiB for programs that do not allocate, and most real programs do; the case for it is the same as the alt stack's, that a facility the program cannot use should not cost a quarter of the chip's RAM. |
— |
backlog-rust (0)
none
backlog-zig (0)
none
experimental (20)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| feature-erlang-frontend-scoping | A | 65 | feature | Erlang frontend — scoping only | — |
| feature-esoteric-ada | A | 65 | feature | Esoteric probe: Ada | — |
| feature-esoteric-cobol | A | 45 | feature | Esoteric probe: COBOL | — |
| feature-esoteric-frontend-probes | A | 60 | feature | Esoteric/legacy frontend probes — umbrella (new category: "esoteric") | — |
| feature-js-frontend-parked | A | 45 | feature | JavaScript frontend — PARKED (architectural wall on the stated goal) | — |
| feature-pascal-schema-types | A | 30 | feature | schema types (ISO 10206 value-parameterized types) — experimental | — |
| feature-r-frontend-parked | A | 45 | feature | R frontend — PARKED (dynamic-runtime language, not a math overlay) | — |
| feature-rust-borrowed-slice-type | R | 45 | feature | Rust frontend — borrowed slice type (&[T], generalized &str) |
— |
| feature-rust-corpus-chess | R | 0 | feature | Rust corpus: the own-written chess engine as Track R's real-world target | — |
| feature-rust-derive-macros | R | 45 | feature | Rust frontend — derive-macro codegen | — |
| feature-rust-drop-move-tracking | R | 45 | feature | Rust frontend — Drop-on-scope-exit + move tracking | — |
| feature-rust-dyn-trait-dispatch | R | 45 | feature | Rust frontend — dyn Trait dispatch for arbitrary types |
— |
| feature-rust-frontend | R | 60 | feature | Rust frontend — umbrella | — |
| feature-rust-macro-rules | R | 60 | feature | Rust frontend — macro_rules! (scope-cut: builtins first) |
— |
| feature-rust-misc-semantics | R | 45 | feature | Rust frontend — integer overflow mode + format-string parser | — |
| feature-rust-rtl-concurrency | R | 45 | feature | Rust frontend RTL — thread / atomics / mpsc shims | — |
| feature-rust-rtl-core-types | R | 45 | feature | Rust frontend RTL — Option<T> / Result<T,E> / Box<T> / Vec<T> |
— |
| feature-rust-rtl-macros-io | R | 45 | feature | Rust frontend RTL — println!/format!/vec!/assert!/panic! runtime |
— |
| feature-wasm-frontend | A | 45 | feature | WebAssembly frontend — statically typed, IR-shaped; experimental | — |
| feature-zig-frontend | Z | 45 | feature | Zig frontend — a working SKELETON; 6 of 28 ordinary constructs compile (re-measured 2026-09-12) | — |
rainy-day (48)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| bug-a-a-set-is-32-bytes-whatever-its-bounds-and-the-ir-opcode-says-so | A | 30 | bug | PARKED BY THE OWNER 2026-09-02 — and since then the mechanism has been BUILT AND MEASURED GREEN but deliberately NOT LANDED: the patch is banked at devdocs/dev/parked-patches/smallset-4-byte-set-storage-class.patch and the question of whether that changes the park is [[decide-a-the-smallset-mechanism-is-built-and-green-does-that-change-the-park]]. The park stands until the owner answers; the tree is at 32 bytes. Original summary follows. — sets are 32 bytes, always, and that is CHOSEN, not tolerated. Deferred rather than rejected: it is solvable at an overhead, and solving it could beat FPC by rebasing to lo (FPC does not, so set of 'x'..'z' costs it 32 bytes for three bits). WHAT PARKING COSTS, measured and accepted: a record containing a small set cannot blit to a typed file — 32 bytes in memory, 4 on disk, every later field shifted — so file of T marshals field-by-field there, and the docs must advise against records-with-sets for file IO. WHAT IT DOES NOT COST: bare-set file IO, which needs no change at all, because our 32-byte mask is a byte-exact ZERO-EXTENSION of FPC's set (measured both size classes) — write 4 bytes when the declared high bound is <= 31, else 32. ORIGINALLY SPLIT OUT of compat-pascal-four-type-sizes-... at frankb-a9's request, because it shares neither cause nor lane nor size with the string[N] third it was bundled with. set of 0..7 is 32 bytes in pxx; FPC gives 4 — a small-set word whenever the HIGH bound is <= 31, and 32 above it (FPC does not rebase to lo, so set of 200..207 is 32 in both). A 32x footprint on the commonest small set, and not a wrong VALUE: every set operation is correct, which is why no differential probe reaches it. NOT a parser change and not a mapping change: 115 tySet sites, 39 IR_SET_COPY/IR_SET_LIT sites, and the width is baked into two contracts rather than a table — defs.inc:2003 defines the kind itself as { 21: Set — 32-byte bitset } and defs.inc:1097 documents IR_SET_COPY as copy full 32-byte set. So it is a codegen/ABI slice: a variable-width set changes the by-value ABI class and every backend's copy, and both IR opcodes' contracts have to change with it. |
— |
| bug-a-the-bare-esp-profile-cannot-compile-any-nilpy-program | A+S | 55 | bug | tools/esp_run_bare.sh --chip esp32c3 <prog.npy> fails on EVERY NilPy program tried, including print(1), with three errors inside units the compiler appends itself: pascal26:1355: error: undefined variable (PXXVarBinOp), pascal26:2029: error: undefined variable (PxxSciDigits17) and, on a program using str()/concat, pascal26:193: error: compiler error: PXXRecordRelease not found in compiler/builtin/promocore.pas. The IDF profile (--platform=esp) compiles the same sources fine, so this is the BARE path specifically. Found 2026-09-20 while trying to build a sensitivity control for [[bug-a-the-nilpy-heap-arena-is-64-kib-of-dead-sram-on-the-esp-idf-profile]], and it BLOCKS that ticket's proof: bare is the only profile where the NilPy heap arena is actually the heap, so it is the only place a deliberately-too-small arena can be shown to fail. The absence is invisible to every tier -- test-esp-bare's fourteen rows are all Pascal, so nothing ever compiles a .npy for bare and the wall generates no red. DIAGNOSED 2026-09-20: the three errors are ONE cause and it is LUMPING, not a missing emission -- builtinheap.pas's {$ifndef PXX_ESP} and {$ifndef PXX_ESP_BARE} blocks each mix a few routines with a genuine bare dependency (a filesystem for PXXStrLoadFile, syscall stdio for the readln block) with ~25 pure pointer/memory routines that have none, and bare loses all of them because they share a guard; the file's own header already says none of the bodies is unimplementable on an ESP chip. BUT FIXING IT DOES NOT BUY A RUNNABLE BARE NilPy PROFILE, which is what this ticket was opened to buy: with every guard neutralised the full demo AND a six-line program both compile completely and stop at CheckBareImageFitsSram, over the stack top by 1,967,792 and 1,958,988 bytes respectively -- an 8,804-byte spread, so ~99.6% of a ~2.2 MB image is fixed NilPy runtime and bare places all of it in SRAM. So SPLIT THE GUARDS ON THEIR OWN MERITS (a bare Pascal program gains the same ~25 routines), and rank the arena control it was supposed to unblock as UNBUYABLE HERE. DISPOSITION 2026-09-20 -> rainy-day/: the question this ticket asked is ANSWERED and the remaining work is a future plan, not a queued fix. What is actionable was split out as [[feature-a-one-guard-excludes-both-the-unimplementable-and-the-merely-adjacent]] (a bare PASCAL program gains the lumped ~25 routines, and that landed for managed records). What is left is not a defect to schedule: bare has no flash mapping, so the whole image is SRAM, and the NilPy runtime is a fixed multi-MB cost because pylib/pyeval are pulled whole. THREE THINGS WOULD EACH RETIRE THIS, and none is a bug fix: bare grows a flash-mapped text section, the runtime pull becomes demand-driven, or the chip's SRAM window grows -- re-run the six-line program (not the demo) and record the chip beside the number. Left ranked would send a seat to a wall measured at ~1.9 MB against a few hundred KiB. |
— |
| bug-nilpy-dict-mutation-during-iteration-is-unobserved-not-raised | N | 35 | bug | Mutating a dict while iterating it is silently unobserved; CPython raises RuntimeError 'dictionary changed size during iteration' | decide-nilpy-dict-mutation-during-iteration |
| bug-p-macpas-conditional-directives-are-ignored-so-both-arms-compile | P | 40 | bug | PARKED IN rainy-day/ 2026-09-05 BY OWNER DECISION -- real, reproducible and intended-someday, not wrong and not unranked-because-unimportant. The MacPas conditional family ({$setc} {$ifc} {$elsec} {$elifc} {$endc} {$definec} {$undefc}) is unrecognised, so every one is ignored and BOTH arms compile; measured against fpc 3.2.2 -Mmacpas, which takes the correct arm. WHAT CHANGED IS THE RANKING, NOT THE MEASUREMENT: supported dialects are FPC and Delphi, other dialects deferred pending real code in active use (see decide-which-pascal-dialects-pxx-targets), and the 31549 setc occurrences behind this ticket are FPC's macOS bindings -- a count of text we do not intend to compile. THE DIALECT-AGNOSTIC HALF WAS NOT PARKED AND IS FIXED: {$MODE MACPAS} is now an ERROR, so this ticket's own repro stops at line 2 instead of compiling the arm it should never have entered. The measured harm recorded below is what sets that severity -- iso and extendedpascal only WARN, because no equivalent measurement exists for them. See decide-which-pascal-dialects-pxx-targets. |
— |
| chore-check-acats-and-nist-cobol85-corpus-availability | U | 20 | chore | Verify that ACATS (Ada) and the NIST COBOL-85 suite are actually fetchable and under a licence we can vendor — both were asserted from memory when costing the Legacy frontends, neither was checked | — |
| decide-abi-portable-vs-target-split | U | 60 | decide | — | |
| decide-ilja-tui-render-model | U | 45 | decide | Track U: four render/input questions Ilja (TUI IDE face) must answer before any code | — |
| decide-nilpy-exec-injects-a-builtins-key | U | 40 | decide | CPython's exec(src, g, l) injects a __builtins__ key into the globals dict; NilPy does not, because it has no module object to put there. So sorted(d.keys()) after an exec differs. Three options: leave it out (today), inject the key with a placeholder value, or inject a real minimal namespace. The fork is what a program that ITERATES the dict should see. |
— |
| decide-segv-runtime-error-default | U | 10 | decide | NARROWED 2026-08-21. The nil-detection half left as feature-a-emitted-nil-checks; what remains is only what the SIGSEGV handler does for the faults a check cannot catch (wild pointers, stack overflow). Fork is no longer default-on vs opt-in but report-and-RE-RAISE (message + core dump + exit 139, default-on) vs report-and-exit-216 (FPC parity, no core dump). Plus: should --mimic-fpc imply --fpc-mem-errors and --fpc-float-errors? | — |
| decide-should-a-stack-overflow-raise-estackoverflow-by-itself | U | 15 | decide | Decide: should a stack overflow raise EStackOverflow by itself, or stay a hand-written hook? | — |
| decide-which-minix-is-the-target | U | 58 | decide | MINIX 2 / early 3.1.x (small, plain, ACK-era C) versus MINIX 3.2+ (which imported the NetBSD userland and build system). These are close to different projects for our purposes, and the choice dominates the cost of the whole lighthouse. Recommendation: MINIX 2 / early 3.1.x. | — |
| design-overloadable-intrinsics | A | 50 | design | Design question: overloadable compiler intrinsics (the Copy precedent) |
— |
| design-record-copy-dynarray-field-semantics | A | 50 | design | Record copy with a dynamic-array field: PXX deep-copies, FPC shares (reference) | — |
| experiment-compile-fpc-as-stress-probe | B | 50 | experiment | Experiment: compile FPC's own source as a pxx stress probe | — |
| feature-a-riscv64-as-a-hosted-first-class-target | A | 10 | feature | pxx has no riscv64 target at all — only riscv32, which exists for ESP-class bare metal. Real RISC-V hardware (notebooks, SBCs) is RV64GC running Linux, so today we cannot build for the machines RISC-V actually ships on. The harness is already ready: run_target.sh handles riscv64, install_qemu.sh installs qemu-riscv64, twatch_web lists it in CROSS_TARGETS — nothing can produce a binary for it. | — |
| feature-additional-cpu-targets | A | 50 | feature | Additional CPU targets (rollup: i386 → aarch64 → arm32 → ESP32/RISC-V) | feature-target-aarch64, feature-target-arm32, feature-target-esp32, feature-target-i386 |
| feature-allocator-quality | A | 50 | feature | Allocator quality: split / coalesce / bins / alignment | — |
| feature-async-auto-backend | A | 50 | feature | Auto stackless/stackful backend selection | — |
| feature-crtl-implement-libc-assumptions | B | 45 | feature | crtl: implement the libc assumptions real-world C leans on | — |
| feature-dwarf-debug-info | A | 50 | feature | DWARF debug info (-g) — phased, x86-64 first |
— |
| feature-eliah-ai-command-rail | B | 45 | feature | feature: Eliah AI command rail + console pane | — |
| feature-fpc-vs-pxx-feature-boundary | A | 50 | feature | Policy: FPC-bootstrap subset vs PXX-only library features | — |
| feature-handle-compacting-heap | A | 50 | feature | Handle-table compacting heap (anti-fragmentation for constrained RAM) | — |
| feature-ilja-tui | B | 45 | feature | Ilja — TUI (ANSI) face | — |
| feature-kernel-matrix-bootroom | E | 50 | feature | Kernel-matrix bootroom: one static PXX binary, swept across many Linux kernels | — |
| feature-mode-delphi-remaining | A | 50 | feature | {$mode delphi} — remaining @-relax edge slices |
— |
| feature-nilpy-runtime-dunder-dispatch-on-variants | N | 45 | feature | Runtime dunder dispatch for a user class held in a Variant | decide-nilpy-runtime-dunder-dispatch-strategy |
| feature-no-ansistring-profile | A | 50 | feature | No-AnsiString / bounded-string profile | — |
| feature-os-targets-bsd-mac | A | 50 | feature | Additional OS targets (BSD / macOS via syscall mapping) | — |
| feature-port-macos | A | 20 | feature | macOS/arm64 target — BLOCKED: needs Apple hardware+software (Mach-O + mandatory signing + libSystem) | — |
| feature-rtl-math-on-crtl-dd-kernels | B | 10 | feature | RTL math.pas on the crtl dd kernels — correct rounding for Pascal too | — |
| feature-rtl-optout-for-lcl | A+B | 45 | feature | Opt out of pxx's own RTL/widget layer (for compiling LCL) — without pulling FPC's RTL | — |
| feature-stackful-coro-port | A | 50 | feature | Port the stackful coroutine backend to all targets | — |
| feature-static-arena-profile | A | 50 | feature | Fixed-static-arena allocator profile | feature-unified-heap-allocator |
| feature-t-host-roles-native-vs-qemu-topology | T | 65 | feature | Track T is becoming multi-host with DIFFERENT PURPOSES per box — xeon runs the matrix, arm32/arm64 rPis exist only as native oracles against xeon's QEMU — but profiles express resource ceilings, not purpose, and nothing compares two hosts' results | — |
| feature-tls13-from-scratch | B | 53 | feature | TLS 1.3 from scratch — syscall-only (Pascal handshake + kTLS bulk) | — |
| feature-track-t-agent | T | 60 | feature | Track T face 2: agentic test manager — reads tstate, crafts tickets, owns the T codebase | feature-track-t-watcher |
| goal-compile-fpc-compiler | A | 50 | goal | 🗼 Lighthouse — compile the FPC compiler (pp.pas) with PXX |
— |
| goal-compile-linux-tinyconfig | C | 50 | goal | 🗼 Lighthouse — boot a Linux tinyconfig kernel built with PXX's C frontend | — |
| goal-compile-minix | C | 50 | goal | 🗼 Lighthouse — build and boot MINIX with PXX's C frontend | — |
| idea-ada-frontend-bare-metal-fit | U | 20 | idea | Ada is the least alien frontend on offer — it descends from Pascal, and pxx already has subrange types with {$R+} range checks raising error 201, which is Ada's Constraint_Error semantics with the default inverted. The cheap subset (no allocation, no tasking) is also the subset embedded Ada actually ships | — |
| idea-cobol-frontend-feasibility-costing | U | 20 | idea | COBOL frontend: parser is cheap (grammar is rigid, records map onto Pascal, unstructured flow already proven by BASIC), but it needs a real fixed-point decimal type — Currency is currently a Double — plus PICTURE-edited MOVE and, for full file support, ISAM | — |
| idea-demo-app-candidates | E | 50 | idea | Demo / test application candidates — selection criteria + catalog | — |
| idea-p-named-parameters-in-the-pascal-dialect | P | 15 | idea | DESIGN SUGGESTION, deliberately parked. Let Pascal calls write f(name := value), reusing the keyword binder NilPy already has. Rejected for now on the grounds that no existing Pascal code can ever use it — the only consumers would be pxx-authored wrappers of Python-shaped APIs, and those can simply BE Python |
— |
| idea-unit-rename-import | B | 50 | idea | uses X as Y unit-rename import (dialect extension) |
— |
| idea-visibility-enforcement | B | 50 | idea | Enforce private/protected visibility | — |
| meta-fpc-error-reporting-parity-cluster | U | 10 | meta | Parking lot for the whole FPC error-REPORTING parity cluster: the SEGV default, stack overflow's 202, --mimic-fpc not implying the --fpc-*-errors flags, tier-2 catchable EAccessViolation, and the per-arch gap. All low prio by the recorded principle that a strict flag governs compilation, not death. NOT in scope: emitted nil checks, which are language-level catchability and stay ranked. | — |
| refactor-p-the-owned-key-in-findtypealias-is-a-boolean-where-the-lexical-key-is-a-distance | P | 30 | refactor | FindTypeAlias ranks candidates on three keys in order -- lexical hop (a DISTANCE), class ownership (a BOOLEAN), uses rank. The Boolean is sound only while AliasVisibleHere admits rows from at most one class, which symtab.inc:408 states as an invariant. Any widening of the class axis retires that sentence and the key silently degrades to first-row-wins, which is declaration order, which is the BASE class. NOT A DEFECT TODAY and not one after the inheritance widening either: fpc refuses the only program that can observe it (Duplicate identifier \"TSel\" -- a derived class may not re-declare a base's nested type name). Recorded because the replacement is known and belongs somewhere a reader will find it: owned becomes hops up UClsParent, mirroring what ScopeHopsToProc already does for the lexical key one position over. |
— |
low-prio (76)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| audit-t-verdict-functions-with-fewer-slots-than-outcomes | T | 30 | audit | Five verdicts in one week reported an outcome the mechanism had not decided | — |
| bug-a-test-tthread-fails-under-full-tier-load-but-never-in-isolation | A | 3 | bug | test_tthread failed once as test-threads#08 in a full tier and cannot be reproduced in isolation — 0 failures in 8 runs — so it is load-dependent, not a regression | — |
| bug-c-the-sizeof-descriptor-walk-answers-from-tyunknown | C | 15 | bug | CENSUS DONE 2026-09-05 (frankC), which this ticket named as its first job: 10932 descriptor walks over 629 C test files plus lua 5.4 and the sqlite amalgamation, and exactly ONE reaches cTk=tyUnknown -- sqlite3.c:137935 sizeof(wsdAutoext.aExt[0]), where the field is void (**aExt)(void) so the pointer default of 8 is THE RIGHT ANSWER, confirmed against gcc. The shape is real and reachable by a real program; the one instance of it is correct by coincidence rather than by the walk knowing anything. NOT rejected/ (the answer is not none) and not worth doing now: the documented fix sends the operand to a fallback whose unknown branch is else sz := 4, so the only existing site would move from a right answer to a wrong one. Low-prio. A SECOND FINDING the census produced: cOK was False in ZERO of 10932 walks -- three sites set it and none fired -- so the decline path this ticket wants to use does not exist in practice yet, and whoever adds one is adding the first. Precondition unchanged: the walk and the fallback must agree on what "I do not know" costs before either may decline. |
— |
| bug-p-the-include-pre-pass-cannot-see-a-switch-directive-written-above-an-ifopt | P | 20 | bug | ExpandIncludes now answers {$IFOPT} from PasIfOptState like the lexer does (4038b32d0), but it tracks DEFINES and not switch DIRECTIVES, so a {$R+} written in the source above an {$IFOPT R+} is invisible to it and the letter answers at its command-line/default state. Only bites when all three are in one file: a switch set in source, an {$IFOPT} on that letter, and an {$I} inside the arm -- then the include is silently dropped and neither arm runs. Real and reproducible; low-prio because the shape is rare and the six default-ON letters, which is where {$IFOPT} guards usually sit, are already correct. | — |
| bug-t-36-ranked-tickets-have-no-track-field-and-their-lane-rests-on-the-filename | T | 45 | bug | tools/progress.sh infers a track from the slug when frontmatter does not declare one. It infers CORRECTLY today -- this is latent, not live -- but the declaration then rests on the filename, so renaming a slug moves the ticket's lane with no diff that says so. Measured across urgent/backlog/backlog_new/unfinished/blocked: 36 of the ranked set carry no track: line at all. check does not report it. | — |
| bug-t-a-campaign-umbrella-has-no-safe-status-to-sit-in | T | 45 | bug | A container ticket for an active campaign has nowhere correct to live. working/ is a per-agent live LOCK, and an umbrella held there for the length of a campaign is a lock that never clears; every other status ready/next scans is claimable, so parking it invites a second agent onto files the campaign owns. The status vocabulary has no term for 'this is a container, not a unit of work'. | — |
| bug-t-a-failing-plain-compile-is-reported-as-a-threadsafe-difference | T | 25 | bug | test-core's language-skeleton loop runs the plain compile with a bare ';' while the very next compile has '|| exit 1'. A failing plain compile does not stop the loop -- it falls through to comparing an empty 'plain' against 'ts' and still fails, but reports '--threadsafe changes the output' for a defect that has nothing to do with --threadsafe. Not a status hole; a diagnosis-quality one. | — |
| bug-t-a-fuzz-finding-cited-by-seed-alone-cannot-prove-a-fix | T | 45 | bug | The csmith campaign cites findings by SEED. A seed only reproduces the same program against an identical generator version AND identical --csmith-args, so a later seed N passes is equally consistent with fixed and with today's csmith emits a different program. Three named open findings (901, 1502, 5004) now pass at HEAD and NONE of them can be closed on that evidence. |
— |
| bug-t-a-job-red-at-baseline-can-never-be-auto-ticketed | T | 55 | bug | A job that is red at BASELINE can never be auto-ticketed, and then reads as furniture | — |
| bug-t-a-one-ulp-move-turns-the-fleet-red-and-outranks-its-own-prio | T | 50 | bug | Float-accuracy assertions in the gated suites make a one-ulp move a CI RED, and a red job is worked at the priority of BEING RED - which overrides the owner's standing rule that float accuracy is low prio. Parking the tickets in float/ does not close this door; only the tests can. | decide-t-per-assertion-subjects-or-accept-the-file-level-label |
| bug-t-a-present-corpus-is-never-checked-against-its-pinned-commit | T | 45 | bug | T: present() compares existence, not the commit the corpus was pinned to |
— |
| bug-t-a-test-targets-timeout-class-is-decided-by-a-substring-and-is-right-by-accident | T | 45 | bug | testmgr's classify() picks a job's timeout class by substring-matching the make -n recipe text. test-nilpy gets corpus/1200s because its recipe happens to contain 'sqlite', 'lua' and 'uforth' -- nothing about NilPy. Delete one test file and the whole suite silently drops to unit/90s, turning every slow-but-passing run into a false RED. uforth already fell through this exact hole. | — |
| bug-t-a-testtmp-binary-name-is-shared-by-two-tests-and-by-two-targets | T | 50 | bug | 117 $(TESTTMP) binary names are written from more than one TARGET, and testmgr runs different targets' jobs concurrently in one scratch root — so two compiles race on one path, which is the ETXTBSY/half-written-binary window the self-host chain already solved with compile-to-unique-name + rename. 15 names are written by two different SOURCES, 6 of those from two targets, where the loser's assertion runs the winner's program. Not a backlog to clean by sweep: the fix is per-recipe and the population is frozen by a devtest so it cannot grow. | — |
| bug-t-a-verify-verdict-is-rendered-with-a-reason-from-a-different-run | T | 55 | bug | A verify verdict is rendered with a reason from a different run | — |
| bug-t-an-acceptance-record-cites-a-sweep-at-an-o-level-the-compiler-rejects | T | 55 | bug | The tkShrLogical rename (314481dd7) records its acceptance as 'byte-identity of emitted output on seven targets at -O0..-O4', in both LOGBOOK.md and BOARD-done.md. The compiler answers unknown option: -O4 and compiler.pas:1034 accepts -O0/-O1/-O2/-O3 only; git log -S shows it has NEVER accepted -O4. So the acceptance cites a level that cannot have been swept. Whatever produced that sweep either skipped the level silently or errored without failing the claim — either way 'we did not measure it' was recorded as 'we measured it and it was fine'. |
— |
| bug-t-check-has-no-aperture-for-a-ticket-whose-body-records-its-own-completion | T | 60 | bug | progress.sh check finds prose blockers whose ticket has CLOSED (STALE-PARK) and prose edges never wired into frontmatter (PROSE-EDGE-NOT-IN-FRONTMATTER). It has no aperture for the mirror case: a ticket whose own BODY records the work as finished while its frontmatter and status line still advertise it as open. Cost a dispatch on 2026-08-30 -- feature-random-library was dispatched on a status line reading 'HW tiers and thread-safe state' when its own log recorded the thread-safe half landing 2026-07-20 and a 2026-08-28 pass concluding 'Nothing here is Track B work. Tier 1 is closed.' |
— |
| bug-t-claim-truncates-a-prose-status-line-the-guard-against-it-runs-first | T | 45 | bug | MEASURED on a live ticket. progress.sh claim replaces a prose - **Status:** <word> — <explanation> bullet with the single word working, destroying the explanation. The guard against exactly this EXISTS, is correct, and RUNS -- sync_status_to_folder (progress.py:2676) matches only the bare one-word form and its comment says truncating an explanation is worse than a stale word. It is called by move_ticket, declines correctly, and is then OVERWRITTEN one line later by set_field(dst, \"Status\", \"working\") (:3019), whose pattern ends .*$ and has no guard at all. Two mechanisms for one concept; the guarded one loses. resolve and unfinish (:3114, :3210) take the same path. |
— |
| bug-t-concurrent-sync-runs-can-squash-two-commits-into-one | T | 45 | bug | With several checkouts syncing at once, tools/sync.sh's rebase-and-retry loop squashed two separate commits into one: the second commit's content survived, its message and its resolves: line did not. Silent — the tree is clean, the push succeeds, and the only tell is a git log one shorter than expected. |
— |
| bug-t-csmith-batch-records-do-not-state-which-o-levels-they-compared | T | 50 | bug | --opts defaults to 0,2, and a batch run without it is written up as clean "across pxx -O levels" — which is true of what ran and false of what a reader takes from it. The aarch64 cross batch of 2026-08-30 (150 seeds, seed-start 300100, no --opts) was cited for months-scale confidence in a backend carrying ten -O3 gate sites, having never built -O3. The fix is that the run's own record must state the levels it compared, not that the next person remembers the flag. |
— |
| bug-t-file-ticket-sh-stages-board-md-but-not-board-brief-md | T | 40 | bug | tools/file-ticket.sh runs tools/progress.sh board-md, which regenerates BOTH devdocs/progress/BOARD.md and BOARD-brief.md, then git adds only BOARD.md. The unstaged BOARD-brief.md makes the following git pull --rebase abort with 'cannot pull with rebase: You have unstaged changes', so the script dies after committing, never pushes, and leaves a worktree behind that it explicitly does not clean up. Reproduced twice; every ticket filed through the tool hits it whenever the board regeneration touches the brief. |
— |
| bug-t-forward-decl-lint-counts-nested-functions-as-globals | T | 45 | bug | gate.sh's fpc seed compiles (forward decls) step treats a NESTED function's name as a global, so any later file using that name as a parameter, local or field is failed for calling something FPC has not seen. Measured: a parameter named argName failed against rparser.inc's ArgName, nested inside RResultClassForRec and invisible outside it. False positive, and it fails the gate. |
— |
| bug-t-fpc-seed-canary-red-cited-lines-that-cannot-contain-the-identifier | T | 30 | bug | One gate.sh quick run reported the FPC seed canary RED with 'symtab.inc(5934,30) Identifier not found ByRefArgNeedsLvalue' — but line 5934 of that file contains an unrelated loop, and the real call sites are at 6185/6186, AFTER the definition at 6099. Not reproducible: fpc compiled the identical tree rc=0 twice by hand and the next gate.sh run was GREEN. Evidence points at the canary reading a stale/other tree state, the same class the fixedpoint step already defends against; a false RED costs an agent a full investigation. | — |
| bug-t-gate-stale-binary-hint-compares-timestamps-so-a-rebase-defeats-it | T | 55 | bug | gate.sh's stale_binary_hint() compares a COMMIT TIMESTAMP (git log -1 --format=%ct -- compiler/) against the binary's FILE MTIME. A git pull --rebase re-dates commits without changing content, so a binary genuinely built from exactly those sources is declared STALE. Observed by frankB: the note fired on a correct binary, a forced rebuild produced a byte-identical sha, and frankA reached the same bytes independently in a separate checkout. The defect is self-concealing — acting on the note produces a green and never reveals the note was wrong. The tree already has the right instrument: tools/compiler_srchash.sh, a content hash over the same file set. |
— |
| bug-t-no-full-suite-refuses-prose-in-a-non-git-compound-command | T | 65 | bug | no-full-suite.sh matches on command TEXT, and its read-only first-word exemption is blanked by any && unless the first word is literally git. So printf '...glob...' >> LOGBOOK.md && git add && git commit is refused for PROSE naming a suite, never for running one. Three sessions hit it independently in one night (frankZ 04:24, frankB twice, once via pgrep -af \"gate.sh full\" matching its own command text). |
decide-t-the-full-suite-hook-refuses-prose-about-the-suite |
| bug-t-nothing-checks-that-two-hosts-run-the-same-suite | T | 60 | bug | plexus's watcher tree was missing five library_candidates that seven's had, so every Track T run on plexus silently omitted those jobs and reported GREEN. Fetched by hand 2026-08-30; nothing prevents it recurring or detects it today. AMENDED the same evening — the acceptance criterion is capability x job, NOT job: a second parity gap was measured where both hosts run the SAME job (csmith-fuzz#arm32) and one claims an ILP32 oracle while the other does not, so job list, count and verdict all agree. A job name is a promise, not a description of what ran, and a job-set diff cannot see it. Fix is persistence, not a new prober: probe_oracle already computes the vector and drops it — emit it into the runs-<host>.ndjson row. Read the amendment before implementing. | — |
| bug-t-optdiff-counts-an-argument-taking-program-as-a-pass-while-sweeping-only-its-usage-path | T | 20 | bug | MEASURED AND SMALL -- filed at p45 with the population unknown, now p20 because I counted it. A 105-program random sample of the 2885-file corpus (fixed seed, reproducible) found ZERO argument-requiring programs beyond the two already known, so the glob pair is plausibly most of this class. Rule of three puts the 95% upper bound at 3.3%, i.e. at most ~96 of 2885 -- a loose bound, and I am not claiming tighter than the sample supports. Kept rather than closed because the mechanism is real and reproducible: a program that exits before doing its work agrees at all four O levels and lands in pass=. THE CENSUS FOUND A DIFFERENT DEFECT INSTEAD, and it is fixed: [ $r0 -ge 124 ] classified 125/126/127 as TIMEOUT-O0, so two object-import tests dying with symbol lookup error: undefined symbol were reported as too slow -- 124/137 are the timeout codes, 125-127 are exec failures meaning the program never started. They now report under their own EXEC-FAIL-O0 line with the rc. That was found ONLY because the census recorded outcomes instead of filtering on a usage-shaped guess; a prefiltered count would have returned 0 and missed it. Also recorded: rc=42 is a SUCCESS sentinel in 16 of the 90 programs that ran, which is the concrete reason rc alone cannot classify this population. |
— |
| bug-t-pin-verify-corroboration-matches-job-names-that-carry-a-shard-count | T | 30 | bug | twatch.py's pin_verify_corroboration() decides whether a pin-verify RED is corroborated by matching job names LITERALLY, and a shard count lives inside the name (test-pascal-conformance#shard5/6). Resplit that suite to 8 shards and every match fails: the caveat silently degrades to the full tier reported on none of those -- still a single run, which is the uncorroborated verdict. No error, no diff, correct about something else. Latent, not firing today; the trigger is a shard-count change, which is a routine thing to do. |
— |
| bug-t-progress-check-cannot-see-an-orphan-fragment-or-a-duplicated-slug | T | 45 | bug | progress.sh check validates ticket CONTENT but not the ticket SET: it cannot see a file with no frontmatter, nor one slug present in two ranked folders. Both occurred together on bug-a-per-cpu-ifdef-chains-in-builtinheap-fail-open — two appends addressed to backlog/ while the ticket lived in backlog_new/ created an empty-headed orphan there, and the ranker then offered the slug twice, once complete-but-analysis-free and once carrying all the analysis with no frontmatter. Neither the checker nor the board reported anything. |
— |
| bug-t-test-fgl-skips-silently-when-the-corpus-is-absent-so-its-gate-row-passes-by-not-running | T | 55 | bug | This checkout has neither fgl.pp nor the fpc-testsuite, and test-fgl SKIPS rather than failing or announcing. A gate line saying test/fgl/objectlist.pas must compile is therefore satisfiable on this box by not running the test at all. Found by frankS while fixing the p70 constraint regression -- it had to run against read-only copies from other clones to get a real answer. |
— |
| bug-t-the-deploy-recipe-builds-a-box-that-reports-but-cannot-measure | T | 50 | bug | trackt setup --fetch-corpus provisions library_candidates/ only. It never runs, mentions or checks tools/install_externals.sh or tools/install_cross_sysroot.sh, so a box built strictly to track-t.md's documented deploy recipe comes up able to publish verdicts and unable to run ten of the jobs behind them. The corpus half self-announces as SKIP; the sysroot half goes RED, and a red is read as a defect in the tree — on seven's first full tier it auto-filed an 18-job cascade naming twelve innocent Rust commits. |
— |
| bug-t-the-duplicate-expectation-ratchet-is-npy-only-and-the-first-escape-was-a-pas-test | T | 55 | bug | npy_cross_target_expectation_devtest.py ratchets duplicated expectations for .npy sources only, though its own COMPILE_RE already matches .pas and .c. The very next divergence was a .pas test duplicated into the SAME two targets the guard was written about, and it cost a p70 regression ticket and a live red on master. Widening the filter naively does not work — the natural population is full of legitimate cross-target asymmetry — but a keyed sub-population of 137 native identical-invocation sources has 15 deliberate exceptions and would have caught this one. | — |
| bug-t-the-gate-checks-binary-freshness-with-a-heuristic-that-cannot-see-the-common-case | T | 55 | bug | gate.sh's stale_binary_hint asks a WORKING-TREE question (is this binary built from these sources) using GIT-HISTORY inputs (mtime vs the newest commit touching compiler/), so it can only ever see divergence that has been COMMITTED. Measured: an uncommitted edit under compiler/ leaves BOTH its inputs byte-identical, so its output is provably independent of the thing it detects -- it is blind to the entire uncommitted present, which includes every agent between a build and a commit. Three lanes read three stale-binary REDs as a master miscompile on 2026-08-31; the hint fired for one. | — |
| bug-t-the-named-rollback-target-cannot-build-the-tree-it-would-roll-back-to | T | 30 | bug | trackt pinstatus names v354 as the fallback pin and pin_is_green() selects it, but v354 CANNOT COMPILE ANY of today's 54 lib/rtl root units (54/54, undefined variable (__pxxblockmove)). Measured across nine pins: v404 fails 2, v375..v403 all fail 14, v365 and v354 fail 54. So the usable rollback depth is ZERO — every historical pin is strictly worse than the current one — and CLAUDE.md's rollback preference is INVERTED: it prefers the green pin, which is the worst target, over the most recent, which is the best. make revert restores only the stable dir, never lib/rtl, so a rollback is incoherent by construction. |
— |
| bug-t-the-o-level-sweep-never-sees-the-third-party-corpus | T | 60 | bug | tools/optdiff.sh diffs -O0/-O1/-O2/-O3 output across 1960 programs from our own test tree and has ZERO references to library_candidates/ or external/. So lua, sqlite, quickjs, zlib, duktape and tcc are compiled at the default level only and are never diffed across O levels. The owner's proof rule leans on the target set being complex enough to constitute a proof — and the part of it carrying that weight is exactly the part the O-level sweep does not reach. | — |
| bug-t-the-quiet-bench-has-produced-nothing-for-two-days-and-never-on-seven | T | 65 | bug | bench.tsv has 17178 rows and NONE are from seven: 11242 borg, 3747 plexus, 2185 xeon — the first and last are retired hosts. plexus last benched 2026-08-28; seven's only attempt (2026-08-30T11:11) returned rc=1 rows=0. The contention guard is working exactly as designed and that IS the problem: it refuses to record a dirty measurement on a busy box, and our boxes are busy because sweeping is their job. Correctness numbers accrue for free from the sweeps; VALUE numbers do not accrue at all. | — |
| bug-t-the-two-watcher-health-checks-disagree-and-are-treated-as-interchangeable | T | 40 | bug | CLAUDE.md gates the widen-your-gate exception on twatch.py --status exit 1 OR trackt.py health DOWN, as if they were two ways to ask one question. They are not: --status reads PUBLISHED tstate (was work swept recently) and health checks for a RUNNING PROCESS (is anything sweeping now). Joined by or, the disagreement resolves silently to down. NO LONGER TRANSIENT: since Track T moved to seven (2026-08-29, recorded at the bottom of this ticket), health on plexus is STRUCTURALLY INCAPABLE of returning UP -- daemon_pid scans the LOCAL /proc and is_daemon requires a local argv of <python> .../twatch.py --clone <clone>, which a watcher on another host can never satisfy. Re-measured 2026-08-31 with T demonstrably sweeping (report 7.5 min old): --status exit 0, health DOWN. So the documented exception was PERMANENTLY ARMED for every dev agent, not open during handovers. HALF FIXED 2026-08-31 by frankT (see the addendum): trackt.py health now answers REMOTE / exit 0 on a box with no local daemon whose PUBLISHED archive is fresh, and still DOWN / exit 2 when it is stale or absent -- so the command can no longer arm the exception structurally, and a DOWN from it means something again. WHAT REMAINS IS THE DOC HALF AND IT IS NOT MINE TO TAKE: CLAUDE.md still joins the two instruments with or, which is the actual subject of this ticket. They answer different questions and the or still resolves a disagreement silently to down. That edit is the owner's. |
— |
| bug-t-twatch-web-lists-a-target-that-cannot-be-built | T | 15 | bug | tools/twatch_web.py lists riscv64 in CROSS_TARGETS, but no compiler backend can produce a riscv64 binary and the test manager never mentions the target. The dashboard therefore carries a column that is structurally empty, and an empty column reads as 'no news' rather than 'impossible'. | — |
| bug-t-two-public-surfaces-answer-how-big-is-the-backlog-differently | T | 30 | bug | The published status dashboard says 338 backlog tickets; tools/factsheet.sh says 351. Both defensible -- factsheet counts backlog_new/, the dashboard appears to break those out alongside '20 experimental'. Not a defect in either, but two public surfaces answer the same question with different numbers and the generator's owner should pick one. | — |
| chore-t-a-stable-gated-red-should-name-pin-lag-before-flakiness | T | 35 | chore | twatch's auto-filed note tells the reader 'this commit CANNOT be the cause ... look at flakiness or box load' whenever a $(PXX_STABLE)-gated job goes red. The deduction is right and the conclusion is wrong: unchanged stable bytes rule out the COMMIT, not the PINNED BINARY, which is stale relative to any compiler fix landed since the last pin. That third branch is missing and it is the common case — the watcher re-files the same already-fixed finding every sweep until the pin moves. | — |
| chore-t-a-standing-collector-cannot-say-so-to-the-ranker | T | 30 | chore | A ticket that is a DESTINATION for findings rather than a task — a standing collector — has no way to say so, so it ranks like work forever. feature-crtl-implement-libc-assumptions said in prose since 2026-07-20 that it has no done state and should not sit in the ready queue; it sat at the head of Track B's queue at p45 for five weeks and was dispatched to an agent as work on 2026-08-28. progress.py reads status and prio, not prose. | — |
| chore-t-a-wikilink-to-a-ticket-that-does-not-exist-is-never-detected | T | 30 | chore | 52 distinct ticket-convention [[wikilinks]] across devdocs/progress resolve to no ticket (71 references; 13 cited by live, non-done tickets). Some are renames leaving a dead trail; some appear never to have been filed, which is work hidden behind a link that looks like a citation. Nothing checks. | — |
| chore-t-board-html-render-is-13s-of-every-ticket-move | T | 40 | chore | tools/progress.sh board-md takes 18.7s, of which ~87% is BOARD.html — a 26MB render every lane pays on every ticket move. Hoisting six re.sub pattern literals out of the inline() hot loop is measured at 18.66s -> 12.99s with byte-identical output. Not landed: progress.py is shared tooling, not Track T's. | — |
| chore-t-fpc-conformance-noise-skews-priority | T | 50 | chore | FPC-testsuite conformance failures auto-file into Track T's backlog at prio 70 by FALLBACK lane, because the failing recipe names tools/run_pascal_conformance.sh and no owner. The suite is a gap-measuring corpus — 170 entries in pxx.skip, failing is its expected state for unimplemented features — so an FPC gap now outranks real work in T's queue, and because the shards run in full they also enter the pin-shadow's blocking set. Owner direction 2026-09-01: FPC compliance is lower priority and running FPC's tests skews priorities. Needs an owner pick between four options; two are cheap. |
— |
| chore-t-lint-a-job-that-runs-a-binary-it-does-not-compile | T | 20 | chore | The second, weaker half of the split_jobs lint: flag any job that RUNS a /tmp binary no line in that job produces. Prototyped and deliberately NOT shipped — it yields 5-7 candidates depending on how recipe lines are segmented, and every one needs individual adjudication. Shipping it half-tuned would produce exactly the noisy guard that gets muted. | — |
| chore-t-lint-fall-open-target-chains-without-the-false-positives | T | 30 | chore | A per-target {$ifdef CPU_x} run with no terminal arm is the shape behind bug-a-per-cpu-ifdef-chains-in-builtinheap-fail-open (5 instances, fixed). Sweeping the tree finds 21 more such runs — and 5 of 5 inspected are NOT defects: they are const tables (an armless target gets an undefined-identifier COMPILE ERROR, i.e. fail-closed) or function bodies with a pre-chain initialiser that is deliberate and documented. A naive lint would have filed 21 phantom tickets, two of them into Track A. The ticket is the three distinctions, not the grep. | — |
| chore-t-make-every-cross-target-row-assert-the-exit-code | T | 45 | chore | 536 cross-target differential rows compare stdout only; 5 capture the exit code. Both operands are runs of the same program, so the exit code is free to add — but run_target.sh returns the EMULATOR's status and signal deaths do not encode identically under qemu-user and a native shell, so a blanket rollout can manufacture diffs on exactly the rows most worth checking. Wants a piloted rollout, one arch at a time, verified against Track T's matrix. | — |
| chore-t-pxx-skip-generic-entries-are-stale | T | 45 | chore | 11 of 21 generic-related entries in test/pascal-conformance/pxx.skip now meet the runner's full contract and are skipped anyway, so the conformance suite under-reports pxx by 11 tests. Two more (tgeneric15/16) now compile and fail at RUN, so their reason strings describe a gap that has moved from parse to runtime. Verified against a compiler whose srchash MATCHES the tree, with %FAIL/%NORUN honoured — a plain compile check would have wrongly called 8 more of them stale. | — |
| chore-t-split-lib-test-into-jobs-that-name-what-failed | T | 45 | chore | One lib-test job bundles several sources, so its tstate key names only the FIRST of them: lib-test#src:test/crtl_exp2.c is really crtl_exp2.c examples/tk/hello.npy +5, and a timeout in the tk step reads as a C-math regression. Split it so a job names what failed. Do it while lib-test is green — the baseline is recorded here. |
— |
| chore-t-test-binaries-hardcode-unsweepable-tmp-paths | T | 35 | chore | 60 /tmp paths are hardcoded in 37 COMPILED TEST SOURCES and written by the test binary at runtime, so no Makefile sweep can reach them and testmgr does not privatize them either. Two concurrent runs still share those files EVEN UNDER testmgr. Split out of chore-makefile-testtmp-parameterize, which closed the recipe half. | — |
| chore-t-the-breadth-line-omits-its-zero-instead-of-printing-it | T | 25 | chore | — | |
| chore-t-the-tier-ladder-ratio-is-stale-by-its-own-criterion | T | 40 | chore | chore(T): re-measure the tier ladder ratio — the matrix grew 37% and the default's own trigger has fired | — |
| chore-t-tools-devtest-is-one-job-that-runs-86-guards | T | 45 | chore | chore(T): tools-devtest#00 is ONE job that serially runs 86 guards, and every number about it is now wrong |
— |
| chore-t-unit-class-est-mem-is-below-what-lib-test-00-actually-peaks-at | T | 25 | chore | testmgr's own advisory, printed at the end of every full tier: lib-test#00 peaked at 596 MB against a 550 MB estimate for class unit. The scheduler admitted it on a promise the box did not have to keep. Raise the CLASSES row to max*1.5, or give the outlier its own class. |
— |
| compat-p-at-over-a-method-pointer-field-yields-the-fields-address-not-the-methods | P | 30 | compat | MODE-ONLY, and every table here compared two different languages. Measured 2026-09-09 with BOTH compilers in BOTH modes across three operand shapes: under {$mode objfpc} pxx and fpc 3.2.2 agree EXACTLY -- @ yields the address for an of-object field, an of-object local and a plain procedural local alike. The divergence exists only under {$mode delphi}, where fpc yields the VALUE for all three while pxx yields the value for a plain procvar and the ADDRESS for the two of-object shapes. Earlier tables compared pxx in its DEFAULT mode against fpc -Mdelphi, which is not a comparison; so frankH's pxx is inconsistent with itself is correct but holds only inside delphi mode, and objfpc -- what this project targets -- has no defect at all. Corpus census done: the only reachable-corpus code writing @<of object procvar> for the value sits under fpc-testsuite/tests/test/jvm/ (tpvardelphi.pp, unsupported.pp), JVM-target files outside any population we build, and the one non-JVM delphi-mode hit (tprocvar3.pp) uses @Class.Method and @objectvar, neither of which diverges. Real, delphi-mode only, needs a two-part change (@ retargeted AND @@ added, refused today), ZERO reachable consumers -- moved to low-prio on CLAUDE.md's rule that compat ranks by how much real code uses it. Reopen with a delphi-mode program we actually compile. |
— |
| feature-t-a-layout-oracle-dimension-the-checksum-is-blind-to-offsets | T | 40 | feature | The csmith oracle is a checksum of the globals, so it is complete for VALUES and structurally blind to LAYOUT: a struct whose members sit at the wrong offsets stores and loads consistently and produces an identical checksum. Predicted 2026-07-13, unacted on, and a real offset bug then survived every batch since -- 443 on 2026-08-30 alone. Proposes a layout dimension: emit offsetof for every member of every generated struct and diff against gcc. | — |
| feature-t-a-second-oracle-dimension-section-alignment | T | 25 | feature | An external alignment oracle: what is left after df98fea47, measured |
— |
| feature-t-a-user-hold-must-survive-a-bulk-re-price | T | 55 | feature | One commit (ab584382e, apply the approved re-triage) erased two user rulings in the same sweep -- the ESP park and the NilPy except-tuple hold -- because each was enforced only by a prio: number with the reason in a # comment, and a bulk re-price rewrites numbers and drops comments. Both instances are now closed on their merits, so this is not urgent; it is filed because the next hold will be recorded the same way unless the recording form changes. |
— |
| feature-t-audit-tests-that-pass-with-the-implementation-removed | T | 40 | feature | frankB wrote a regression test for bug-b-resolver-sends-localhost-to-the-wire, got eight green rows, then reverted the fix to control it — and the test still passed, every row. This box's systemd-resolved is itself RFC 6761 compliant and synthesises the localhost subtree, so the broken code returned the right ANSWER and merely emitted 20 DNS queries to get it. A value assertion was testing systemd-resolved. Three instances of this shape landed in one night. This ticket is the sweep for others. | — |
| feature-t-check-flags-a-lane-blocker-that-has-no-in-edges | T | 40 | feature | prio propagates down dependency edges, so a ticket with in-degree zero inherits nothing — and a ticket that blocks a LANE rather than a ticket never gets an edge, because blocked-by: would be a false claim. Such a ticket under-ranks itself permanently and no checker sees it: from the ranker's side an in-degree of zero is indistinguishable from a leaf. Proposal: progress.sh check flags a ticket whose body names a track as its beneficiary and has no in-edges. Threshold MUST be calibrated against the live board before landing. |
— |
| feature-t-commit-trailer-hook | T | 60 | feature | Two thirds of agent commits (840 of 1262 in one night) carry no Claude-Session trailer, so a collision cannot be attributed to the sessions involved. CLAUDE_CODE_SESSION_ID is in every agent's environment; a prepare-commit-msg hook can append the trailer unconditionally instead of relying on the voluntary act that is already failing 2 times in 3. | — |
| feature-t-dead-commit-is-a-closed-stock-and-a-counter-nobody-will-read | T | 30 | feature | The 350 dead citations are unrecoverable and finished; the risk is now the counter | — |
| feature-t-detect-ticket-clusters-that-share-a-construct | T | 45 | feature | Prio propagates only down declared blocked-by: edges, so a ticket that is upstream of a family it was filed AFTER can never inherit their priority. Measured 2026-08-31: shr produced ten tickets, eight closed individually over two months priced 30-60, while the one describing their shared cause sat at prio 20 — the lowest of all ten. Add a scan that groups open AND closed tickets by shared construct tokens and flags a cluster whose members were fixed one at a time. |
— |
| feature-t-grade-a-pin-instead-of-gating-it | T | 85 | feature | Grade a pin instead of gating it, and say what a red pin is known to break | — |
| feature-t-lint-token-text-compared-against-a-keyword | T | 35 | feature | Make the never-true guard a lint instead of an audit | — |
| feature-t-nilpy-cpython-differential-fuzzer | T | 45 | feature | NilPy differential fuzzer — generate NilPy programs, diff pxx output against CPython as oracle | — |
| feature-t-pasmith-rung-selftest | T | 30 | feature | A fuzz rung that has only ever been SILENT is indistinguishable from one that does not work. Proposes a --selftest that proves each rung's fold actually observes its construct, by MUTATING the generated program rather than by rebuilding an old compiler — cheaper, needs no checkout, and applies to rungs that were never written against a specific fix. | — |
| feature-t-record-host-cpu-features-in-tstate | T | 20 | feature | tstate records host, sha, tier, wall and compiler_sha256 — nothing about the machine. So 'can we emit FMA?' could not be answered from the repo and needed an ssh into plexus. Record CPU model and the x86-64 feature level per host, once, in the host json. | — |
| feature-t-track-the-rel8-displacement-budget-so-a-tight-jump-is-visible-before-it-breaks | T | 40 | feature | A standing row that compiles a fixed program set with PXXDBG=a.rel8max and alerts when the slack to the +-128 one-byte-jump limit falls below 16. Measured 2026-09-01: the max displacement exercised across self-host plus the whole quick tier is 101, i.e. 27 bytes of headroom, and it is emitted by the --threadsafe lock path -- the region Tracks A and O are actively growing. It is program-INDEPENDENT (54 plain / 101 threadsafe on every Pascal and C source tried), so the number is a property of the runtime/prologue emitters, not of a test corpus. This measures BUDGET only; frankA's cd4af7824 is the sibling class it cannot see. | — |
| feature-t-uforth-bench-on-the-watcher-idle-phase | T | 25 | feature | tools/uforth_bench.py is standalone + a make target, so uforth rows only exist when a human types it. Hang it off the watcher's idle bench phase so rows land per-sha automatically — which is also the only way to get the quiet-box baseline the harness has never had, and the instrument for the open slow-creep question. | — |
| feature-t-uforth-bench-restore-the-elfhash-outlier | T | 15 | feature | blocktest-elfhash SKIPs in the uforth bench: blocktest.fth needs uforth's block-word preamble (FIRST-TEST-BLOCK / LIMIT-TEST-BLOCK / [?IF]) that tester.fr alone does not supply. It is the tracked ~100x-slow outlier, so while it skips the harness has no visibility on the worst case. | — |
| feature-twatch-full-tier-coverage-age | T | 35 | feature | No signal distinguishes "full tier is lagging" from "full tier never completes" | — |
| idea-t-watch-the-closest-call-approach-not-the-image-size | T | 40 | idea | The obvious guard against xtensa reach failures is a watch on image size, and it would NOT WORK: measured 2026-09-01, the 622444B call0 image FAILS and the 556908B windowed image BUILDS, both over CALL8's 524288 -- the larger one is the one that builds, because the condition is max caller->callee distance and size is only a proxy. Watch closest approach to +-512 KiB across call sites instead; the xtensa backend already computes it to emit its refusal, so this is a report, not a new analysis, and it changes no codegen. | — |
| meta-t-dev-throughput-and-track-a-t-integration | T | 30 | meta | META: development is wait-limited, not token-limited. Dev tracks stop running suites; T owns breadth and its report LATENCY becomes the product. Coordinates the tooling tickets that get us there. | — |
| refactor-t-the-automated-pin-stages-the-stable-tree-by-a-hardcoded-path | T | 20 | refactor | NOT a present fault -- verified correct today. The automated pin path in tools/testmgr.py stages the stable tree with git add -u <root> plus an explicit git add <root>/default/builtin. The second call is what saves it, and it saves it by NAMING the one directory that has ever needed saving. git add -u stages tracked files only, so any FUTURE directory added under the stable root is silently left untracked in the pin commit, exactly as builtin/ was before that line existed. Correct by hardcoded path rather than by rule. |
— |
| task-t-a-corpus-tree-absence-should-be-counted-not-just-echoed | T | 45 | task | A test row whose corpus tree is absent echoes SKIP and passes, and nothing counts how many did. Measured on this box: test-core's crtl_tiny_regex_match row was UNGUARDED, so a missing library_candidates/tiny-regex-c hard-errored and — because make stops at the first failing recipe line — took 844 of test-core's 1745 compile rows (48%) with it, with no indication in the log of how much had not run. Guarding it (2026-09-01) fixes that and buys the opposite failure: testmgr's own TIERS comment records test-fgl printing SKIP and PASSING for its entire life without running once. Both failure modes are live in this repo TODAY. The missing mechanism is the same one test-c-abi-mixed-link now has: count the skips and report N of M measured, K skipped, so a box quietly running half the suite is visible in the verdict instead of indistinguishable from a green one. |
— |
| task-t-the-c-corpus-is-two-rungs-not-four-and-a-missing-tree-reports-pass | T | 45 | task | Of the four C corpora the repo treats as its real-program coverage -- lua, zlib, quickjs, tcc -- only lua and zlib are in a testmgr tier. test-quickjs exists in the Makefile and is enrolled in NO tier; test-tcc does not exist at all (TCC_SRC appears 0 times) though install_lib_candidates.sh can fetch it. And test-quickjs self-skips exit 0 on a box without the tree, so enrolling it alone would still assert nothing while reporting success. | — |
known-incompat (6)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| bug-a-readln-diverges-from-fpc-on-a-malformed-number-and-on-a-char-read-from-an-empty-line | A | 25 | bug | Two divergences left over from the 2026-09-18 readln cross-target census, both measured on x86-64/i386/riscv32/arm32/aarch64 (all five agree with each other) against FPC 3.2.2 {$H+}. (1) MALFORMED NUMERIC INPUT: FPC raises runtime error 106 and halts; we return a value and carry on — - with no digits -> 0, x9 -> 0, 300 into a Byte -> 44, 40000 into a SmallInt -> -25536. (2) A CHAR READ FROM AN EMPTY LINE: both compilers hand back #10, and then FPC's readln SKIPS A FURTHER WHOLE LINE — on input \\nALPHA\\nBETA\\n, readln(c); readln(s1) gives s1=[BETA] under FPC and s1=[ALPHA] under pxx. FILED HERE RATHER THAN IN backlog-core BECAUSE (2) IS CHOSEN: FPC discards a line the program never saw, ours does not, and on par with the LANGUAGE, not with FPC prefers the answer that loses no data and leaves a mistaken program's mistake visible. (1) is the one with a real question in it and it is a POLICY question, not a mechanism one — see below. |
— |
| bug-p-a-generic-routines-implementation-type-parameters-are-not-checked-against-its-interface | P | 30 | bug | KNOWN DIVERGENCE, not a defect — SETTLED 2026-09-05 by measurement, and the reason is not the one this ticket assumed. A unit may declare generic procedure Test<T> and implement generic procedure Test<S>; pxx accepts the differing impl-side spelling, which is the same deliberate rule pxx.skip already records for tgeneric20 and tgeneric30 — the implementation side of a generic routine need not echo the interface's type-parameter spelling. A RENAME cannot mislead, because both spellings denote the same single position. A SWAP could, and would make this gap: accepts-invalid instead — but it is UNREACHABLE: no two-parameter generic routine parses at all, and the control that proves it has neither a swap nor a rename (generic procedure Pair<T, S> identical in both sections still refuses with expected '>' before ','). Filed as bug-p-a-generic-routine-supports-exactly-one-type-parameter. tgenfunc17/18 are now skip-listed wontfix: dialect-pass WITH A TRIPWIRE: when the one-parameter limit is lifted the swap becomes reachable and this question re-opens. |
— |
| bug-p-single-plus-single-is-typed-and-computed-at-double-width | P | 40 | bug | KNOWN DIVERGENCE, not a bug (owner, 2026-09-02). The measurement in this ticket is TRUE and reproducible; it is not a defect. Evaluating Single + Single at double width is a legitimate implementation choice and is strictly MORE accurate, and a program that stores the result in a Single gets FPC's exact bytes -- measured, s := a + b gives 0.300000012 on both. Nobody computes a wrong value. Two behaviours are CHOSEN, not tolerated, and neither compiler is wrong: SizeOf(a+b) is 8 where FPC says 4, and an overloaded P(a+b) picks the Double arm where FPC picks Single -- both are TRUE statements about a pxx expression, exactly as FPC's answers are true about an FPC one; SizeOf reported correctly about the actual type, which is why the operator exists. A caller needing the narrow type writes Single(a+b). Matching FPC would mean discarding precision we already have to reproduce its rounding, which is FPC-parity chasing rather than language conformance. |
— |
| compat-pascal-directive-in-comment-ignores-nested-comments-off | P | 5 | compat | KNOWN-INCOMPAT, chosen (2026-09-02). With nested comments OFF (delphi mode) a {$...} inside a brace comment does not end the comment here and does in FPC -- the LAX direction, so pxx accepts sources FPC rejects, which CLAUDE.md states is not a defect. No correct program is refused and nothing computes a wrong value; relying on it means relying on a lexer accident to change what is code. Independently: the one-line fix was tried 2026-08-19, is in the right place, and BREAKS THE SELF-BUILD -- evidence about cost, and the second reason rather than the first. | — |
| incompat-b-crtls-dns-parser-refuses-two-malformed-packets-glibc-accepts | B | 5 | incompat | KNOWN-INCOMPAT, chosen (2026-09-04). crtl's resolver refuses two malformed DNS inputs that glibc accepts, both measured against glibc on this box: (1) ns_name_unpack() refuses a compression pointer that points FORWARD -- glibc follows it and returned 2 where crtl returns -1; (2) res_nsend() discards a datagram whose QR bit is clear -- glibc accepts it, and a probe that sends a decoy with the right id and QR=0 followed by the real reply gets 6.6.6.6 under glibc and 10.1.2.3 under crtl. Both crtl answers are the RFC-conforming ones (RFC 1035 4.1.4: a pointer names a PRIOR occurrence; RFC 1035 4.1.1: QR distinguishes a query from a response) and both are strictly safer, since each glibc behaviour lets bytes an attacker chose reach a caller. No conforming server emits either shape, so no correct program is refused. Chosen, not tolerated: matching glibc here would mean deliberately accepting input the protocol says is invalid. | — |
| nilpy-an-annotation-is-a-declaration-not-a-hint | N | 0 | nilpy | MEASURED 2026-09-09: NilPy treats a variable annotation as a DECLARATION where CPython treats it as a hint, uniformly for fields and locals. self.a: float = t with t=2 prints 2.0 against CPython's 2; a: float = t in a plain def does the same, which is what shows it is the annotation rule and not a field rule. CHOSEN, not tolerated: the annotation is how a NilPy field gets a static type at all -- the compiler's own diagnostic says annotate it (self.a: int = ...) -- so an annotation that did not determine storage would make that advice meaningless. ONE ROW DESERVES A SECOND LOOK and is named below rather than buried: self.a: int = t with t=2.7 stores 2, silently. |
— |
float (23)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| bug-b-f-fixed-point-rounding-of-a-tie-goes-down-where-fpc-goes-up | B+F | 15 | bug | Fixed-point rendering of a decimal tie rounds the OTHER WAY from FPC: Format('%.2f', [1.005]) is 1.00 here and 1.01 there, likewise 2.675 -> 2.67 vs 2.68. Ours is the correctly-rounded answer for the actual Double (1.005 is 1.00499999999999989); FPC rounds the decimal literal as written. Pre-existing in FmtFixed, so it hits '%f', '%n', '%m' and FloatToStrF's ffFixed/ffNumber/ffCurrency alike. Last-digit-only — Track F by definition. | — |
| bug-b-fpc-numeric-compat-floor-ceil-return-float-currency-is-double | B+F | 25 | bug | Two FPC numeric divergences in lib/rtl: Math.Floor/Ceil return Double where FPC returns Integer (and Floor64/Ceil64 are missing), and sysutils declares Currency = Double where FPC's is a fixed-point 4-decimal Int64 — so a money type cannot represent 0.10 | idea-cobol-frontend-feasibility-costing |
| bug-b-rounding-api-gaps-setroundmode-roundto-lround | B+F | 35 | bug | Per-language rounding DEFAULTS are all correct (Pascal banker's = FPC, C round() half-away = gcc, Python round() = CPython incl. round(2.675,2)=2.67) — but the escape hatches are missing: no SetRoundMode/RoundTo/SimpleRoundTo in lib/rtl/math.pas, no lround/llround in crtl | feature-a-expose-rounding-mode-intrinsic-to-pascal |
| bug-f-riscv32-sin-loses-about-nine-digits-near-a-zero-so-mathdemo-self-reports-failures | F | 20 | bug | examples/mathf/mathdemo.pas prints ALL OK on i386, arm32 and aarch64 and FAILURES on riscv32 AND xtensa, on one row of 66: real = double (sin) gives -0.000000087 where the others give 0.000000000. The two failing targets produce BYTE-IDENTICAL output, and they are exactly the two SOFT-FLOAT targets — so this is not per-backend codegen but one shared implementation, compiler/builtin/softfloat.pas. Accuracy near a zero, probably argument reduction. One fix helps every soft-float target. |
— |
| bug-f-xtensa-writes-a-double-in-single-width-digits-and-a-2-digit-exponent | F+S | 25 | bug | WriteLn of a Double on hosted xtensa prints 10 significant digits and a 2-digit exponent (3.500000000E+00) where every other target prints 17 and 3 (3.5000000000000000E+000). The VALUES are correct -- this is digit count and exponent width only. Last remaining real divergence in the hosted-xtensa differential. | — |
| bug-n-pylib-cannot-reach-the-rtl-power-so-complex-magnitude-loses-ulps | N+F | 25 | bug | pycomplex_pow computes |z|**b as exp(b*ln|z|) — two roundings — where CPython calls pow() directly, so (-8.0) ** 0.5 gives an imaginary part of 2.8284271247461894 against CPython's 2.8284271247461903 (~4 ulp). The cause is structural: pylib lives in compiler/builtin and cannot reach the RTL's correctly-rounded Power, which is why it carries its own series ln/exp in the first place. |
— |
| bug-nilpy-complex-pow-is-a-few-ulp-off-cpython | B+F | 20 | bug | (-8) ** (1/3) answers a complex now, but its imaginary part is 5 ulp low: pylib cannot uses math, so complex pow rides hand-rolled sqrt/sin/cos beside PyMathLn/PyMathExp. Every other line of the complex oracle matches CPython exactly. |
— |
| bug-nilpy-float-pow-loses-a-ulp-vs-libm | N+F | 20 | bug | 2 ** 0.5 is not math.sqrt(2) — the float power is computed as exp(y·ln x) |
— |
| bug-nilpy-float-power-is-a-ulp-off-the-rtl-already-has-the-fix | B+F | 20 | bug | NilPy's ** with a fractional exponent still uses exp(y*ln(x)), so 2**0.5 != math.sqrt(2); lib/rtl/math.pas's Power was fixed with a double-double kernel for exactly these cases and names 2^0.5 in its own comment |
— |
| compat-pascal-a-whole-valued-double-variant-writes-a-trailing-point-zero | P+F | 20 | compat | writeln(v) on a Variant holding a whole-valued Double prints 15.0 where FPC prints 15. Rendering only — the value is right, and the same Double in a plain variable renders identically to FPC. Float FORMATTING, so it parks here. |
— |
| compat-pascal-strict-fpc-unmask-fp-exceptions-two-flags | A+F | 30 | compat | FPC unmasks the FP exceptions every ISA leaves masked: 1/0 is a runtime error there and Inf here, and Floor(1e30) raises EInvalidOp where pxx now saturates. Decided that pxx keeps IEEE masked semantics by default and FPC's behaviour goes behind opt-in flags — TWO of them, because div-by-zero -> runtime error 208 is nearly free (an FPU control word bit) while Floor raising EInvalidOp costs sysutils, +127 KB code and +33 KB bss on every uses math program. |
— |
| decide-default-float-output-format-and-constant-precision | U+F | 10 | decide | decide: should WriteLn's default float format follow the STATIC type, and should untyped float constants evaluate at Single precision? | — |
| decide-is-real-a-double-or-fpcs-80-bit-extended | U+F | 30 | decide | writeln(3.14159) prints 3.1415899999999999E+000 in pxx and 3.14158999999999999993E+0000 in FPC, because pxx's Real is a 64-bit Double and FPC's is the x87 80-bit Extended. Making them agree means implementing an 80-bit float type; keeping them apart means declaring the difference permanent. Both are defensible and neither is a bug. |
— |
| docs-publish-the-three-language-rounding-table | D+F | 30 | docs | One backend implements three different, correct rounding rules — Pascal ties-to-even, C half-away-from-zero, Python ties-to-even on the exact decimal — each verified against fpc/gcc/CPython. That is a differentiator and it is documented nowhere; it currently lives only inside a Track B ticket | — |
| feature-a-expose-rounding-mode-intrinsic-to-pascal | A+F | 30 | feature | __pxx_fesetround/__pxx_fegetround exist and flip MXCSR, but only the C frontend can reach them, and off x86-64 they are an accepted no-op returning 0 — so Pascal cannot get a SetRoundMode that actually sets the mode | — |
| feature-a-extended-is-an-alias-for-double | A+F | 25 | feature | UMBRELLA for the Extended cluster. Extended is accepted and silently mapped to Double, so FPC code that uses it deliberately gets a quietly worse answer with no diagnostic. Owner ruled 2026-08-30 that real 80-bit Extended WILL be implemented eventually; the whole cluster is parked in float/ until it is worked as one consolidated session. |
— |
| feature-b-hardware-sqrt-on-aarch64-and-arm32 | B+F | 20 | feature | Sqrt is one sqrtsd on x86-64 (15x faster than the software path and correctly rounded by IEEE mandate). aarch64 fsqrt and arm32 vsqrt are the same one-instruction win and both run here under qemu, so the change is verifiable on this box. The portable SqrtSoft stays as the fallback for riscv32/xtensa. |
— |
| feature-b-rtl-lnxp1-fpc-compat | B+F | 20 | feature | FPC's math unit exports LnXP1(x) = ln(1+x) and pxx does not. The implementation already exists as of 2026-08-15 — LnP1, added as an internal helper for the hyperbolic family — so this is an interface line and a name, not an algorithm. Note WHY the name matters: Log1p would hijack libc's through pxxcio, LnXP1 does not. |
— |
| feature-extended-type-support | A+F | 25 | feature | SUPERSEDED 2026-08-30 by [[feature-a-extended-is-an-alias-for-double]], which is the designated umbrella for the Extended cluster. Kept as a gravestone because it is cited from three places; its one unique constraint (the RTL ships Single + Double overloads only, on purpose) has been folded into the umbrella. | — |
| feature-opt-complex-packed-double | O+F | 35 | feature | Complex as a packed-double XMM value (SSE2/SSE3) | — |
| feature-opt-float-format-fast-path | O+F | 30 | feature | Fixed-point float formatting is 4.7x slower since it started taking its digits from the double's exact decimal expansion (8.8us vs 1.85us per %.2f, measured over 200k). Correct now, and worth a fast path for the values that provably cannot sit near a midpoint. | — |
| feature-opt-float-register-temporaries | O+F | 20 | feature | float kernels: -O3 now 1.97x vs FPC (was 4.2x); residual = the rax value model — multi-session xmm-resident rewrite | — |
| meta-float-accuracy-policy | U+F | 60 | meta | Standing index for the float-accuracy category the owner asked for on 2026-08-16: collect every float/exact-float ticket in one place, do NOT fix them piecemeal, and decide once how the fast/exact split should work — because the shipped policy (fast by default, 1-2 ulp never a bug) and the shipped TESTS (bit-exact CPython .expected) currently contradict each other. | — |
done-followup (3)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| bug-t-corpus-regex-invents-phantom-tree | T | 55 | bug | CORPUS_RE captures punctuation out of recipe PROSE and invents a corpus tree that cannot exist, silently skipping the job — twice: 'stb)' 2026-07-31, and 'zlib.' 2026-09-06 after the first fix removed ')' and kept '.' | — |
| feature-async-language-surface | A | 50 | feature | Async language surface + stackless coroutine backend | feature-cross-target-feature-parity |
| feature-string-model-tyfixedstring | B | 50 | feature | String model overhaul: tyFixedString + managed string + Str/Val |
— |
decided (151)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| README-agent-decisions | A | 50 | README | Agent-made Track U decisions — the authority, and how to read them | — |
| decide-1-0-scope-promise | A | 55 | decide | DECIDE: version scheme — pin count / N, not semver | — |
| decide-3rd-party-vendor-vs-fetch | U | 45 | decide | Policy: how to carry dependency-grade third-party source — vendor in-tree vs fetch-gitignored vs system-dynamic | — |
| decide-a-cross-unit-define-name-and-semantics | U | 40 | decide | A define that crosses unit boundaries is order-dependent BY CONSTRUCTION — {$DEFINEGLOBAL} reads as 'global' while the mechanism is claim-and-skip. Four questions (name, undefinability, scope, visibility to earlier units) must be settled before anyone builds it, and nothing currently pulls on the feature: its motivating case was closed as synthetic. |
— |
| decide-a-how-should-the-nilpy-managed-finalize-re-enter-the-heap-lock | U | 40 | decide | DECIDED 2026-09-06 BY THE OWNER: arm (a), THE REENTRANT LOCK -- his own 2026-08-21 unpark trigger ('a deadlock, or a new managed member kind whose release cannot be hoisted out of the lock') has fired. Closes THREE of the six open leaks under one lock, not one, and unblocks del. The hot-path objection was formed before the magazine landed and has never been measured; take that number, do not re-open on it. Measured 2026-09-02: unifying the managed-field walk so NilPy gets it too FIXES the 399524 kB -> 7844 kB leak and DEADLOCKS on one twelve-line program -- a class whose field is a class instance, one instance, one thread, exit 212. The recursion is PXXClassFinalizeManaged re-entering itself through kind 6; a list or dict field does NOT do it, and Pascal structurally cannot. Two ways out and neither is a default. (a) The reentrancy half of feature-a-reentrant-heap-lock-and-per-thread-arenas, which that ticket records as PARKED BY THE OWNER -- unparking it is his call, not a session's. (b) Defer the nested release: the kind-6 arm pushes onto a per-thread pending list and the codegen wrapper drains it just after EmitReleaseHeapLock, needing no change to the lock primitive but moving a finalizer to after the outer walk, an observable ordering change. Blocks bug-a-nilpy-under-threadsafe-still-leaks-every-class-field-and-it-cannot-ride-on-the-pascal-fix. | — |
| decide-a-should-a-pascal-program-compiled-to-an-object-run-its-main-body-when-a-foreign-program-loads-it | U | 55 | decide | DECIDED: A -- an object runs the program body, same as --shared already did. Settled by archaeology rather than by preference, after the owner said the question as filed was not answerable without knowledge they do not have. THE FORK WAS NOT REAL: --shared has run unit init AND the program body since the shared-library fix (the test-shared row says so in its own name), so the two library-shaped outputs disagreed about the same source construct and only one of them had a reason. The test_emit_obj.pas comment I treated as pinning a design was written in 41045d7b4, the commit that INTRODUCED the object writer, describing behaviour that existed only because nothing ran an object's initialisers yet -- and its own wording guards against initialisation SILENTLY starting, which licenses a considered change. library foo; does not parse (feature-p-a-pascal-library-unit-does-not-parse), so program is the ONLY way a user can currently express this. Landed with the comment rewritten; i386/xtensa/riscv32 remain as bug-a-an-i386-emit-obj-object-still-never-runs-its-initialisers. |
— |
| decide-adopt-a-second-string-model-or-refuse-utf16-honestly | U | 62 | decide | feature-unicodestring-model [A p62] says in its own body that this is a MODEL DECISION, not a function to write -- and its title offers the alternative outright: a real UTF-16 model, or an honest refusal. pxx has one string model (bytes, CP_UTF8 passthrough) and the RTL is already candid about it at the declaration: UTF8Decode/UTF8Encode are the identity, WideChar casts to a 2-byte ordinal. Adopting UTF-16 is a second model in a compiler whose whole design pushes generality DOWN into one substrate. Refusing means fcl-json's \uXXXX surrogate path stays uncompilable. Neither is derivable from the code or from a sensible default, so it is Track U. | — |
| decide-arm-track-t-autopin-the-evidence-gate-cannot-pass-as-written | U | 90 | decide | The owner designed Track T auto-pin on 2026-08-08 (option A: baseline allowlist, K>=2 consecutive qualifying shas, auto-rollback) and it was built and started in SHADOW MODE, to be armed once its calls could be compared against what a human actually blessed. Measured 2026-09-09: the machinery has worked correctly for a month -- 123 WOULD PIN verdicts, 57 of them at ZERO reds, most recently 2026-09-07T21:01Z -- and has never been armed, because THE COMPARISON THAT WOULD ARM IT CANNOT BE RUN. The shadow evaluated 647 of 13,938 commits (4.6%); humans pin at whatever HEAD is when they decide. Overlap between 94 human pins and 123 shadow-cleared shas is ZERO against ~4 expected by chance. The two processes sample disjoint sets of shas, so 'compare a week of shadow verdicts against human pins' is unsatisfiable AT THE SHA LEVEL. This is a gate that cannot pass, and it has held the fleet's only automated pin path for a month. | — |
| decide-assertion-default-vs-fpc | U | 25 | decide | pxx evaluates Assert() by default; FPC ignores it unless -Sa. So Assert(False) raises EAssertionFailed here and is a no-op there — code that passes its own test suite under FPC can die under pxx, and vice versa. With -Sa the two agree exactly, so this is purely a question of which default we want. Options: keep ours, match FPC, or add {$ASSERTIONS}/-Sa and pick a default. | — |
| decide-assertions-directive-and-message-format | U | 40 | decide | FPC compiles Assert OUT unless -Sa/{$ASSERTIONS ON} and appends '(file, line N)' to the message; pxx always evaluates and omits the position. Adopt both, neither, or one? | — |
| decide-builtin-and-library-code-sharing | U | 30 | decide | A builtin unit and lib/rtl cannot share code today: moving the shared part down breaks library READABILITY (you must be able to step into sysutils and read it straight through), and letting a builtin use the library collides in NilPy's flat unit scope. The float core is being copied because of it. Review when the next clash lands — not a blocker for anything now. | — |
| decide-class-namespace-scoping | U | 65 | decide | Decide: how should two libraries be allowed to export the same class name? | — |
| decide-classinfo-returns-our-blob-or-nothing | U | 35 | decide | TObject.ClassInfo is the last unimplemented member of the TObject API. Returning our own class blob is right for identity comparison and wrong for anything that walks FPC's TTypeInfo layout. Three options: emit it as our blob, refuse it, or route it through the typinfo facade. Needs the owner's call on how far reflection parity goes. |
— |
| decide-constructor-exception-cleanup-semantics | A | 60 | decide | DECIDE: constructor-exception-cleanup semantics (auto-Destroy on failed Create?) | — |
| decide-cross-language-qualifier-syntax | U | 50 | decide | DECIDED 2026-08-16: none of the three proposed syntaxes. The escape is uses './mymath.c' as cmath; + cmath.cube(...), which feature-uses-alias-as shipped 2026-06-30 and which reaches foreign symbols because the alias maps to the REAL unit's Strs[] index. Verified on pinned. Bare uses './x.c' stays unbound deliberately. The originating bug is a docs fix, not a compiler one. |
— |
| decide-crtl-libm-glibc-bit-parity | A | 50 | decide | — | |
| decide-deploy-key-on-via | W | 60 | decide | RESOLVED 2026-08-27 — via's account-level GitHub key is INTENTIONAL and the risk was already calculated by the owner ("this is actually good news / all working as intended. security risks all calculated"). No credential change needed; the scoped deploy key is unnecessary. Read this before treating that key as a finding — three sessions burned an afternoon routing around a wall that was not there, and the credential is not a discovery. Background: the premise of the original decision was FALSE. via has carried an ACCOUNT-level GitHub key since 2026-06-22 with read (and by ownership, write) reach into the pxx COMPILER repo. So there was never a no-push-key posture, the owner is accepting a risk he already carries in a WIDER form, and adding the scoped key while removing/scoping ~/.ssh/id_ed25519 is a net risk REDUCTION rather than a trade against velocity. Original decision: via gets a deploy key with write access scoped to the website repo, accepting a security cost to unstall Track W. Records the bound the decision was made INSIDE — one repo, never the compiler repo, never a PAT — because that bound is the whole difference between the accepted risk and a much larger one, and it is the part that erodes silently. |
— |
| decide-dns-libc-backend-shape | U | 40 | decide | Track U: how should a libc-backed DNS resolver be reached from libc-free static ELF? | — |
| decide-does-a-c-function-always-use-the-c-abi-or-only-when-a-pascal-program-uses-it | U | 65 | decide | RULED OPTION A, 2026-08-31, after the trigger fired the same day. A C function ALWAYS uses the C ABI: delete the third clause of CProcUsesCAbi (symtab.inc:11599). SUPERSEDES this ticket's earlier same-day ruling of 'neither yet, deferred on a trigger' -- the trigger was feature-a-object-output-for-i386-arm32-and-aarch64, it landed (be4442d9b), and the measurement is unambiguous. gcc -m32 calling a pxx i386 object: under the landed gate a standalone C TU gets i_ii(1,2)=21 (arguments REVERSED), every double argument or return -nan, i_id(2,3.5)=1074528256; the same signatures as Pascal cdecl are all correct. Reproduced independently on compiler 7821dd062028. Two supporting findings: the internal convention buys NO performance -- on i386 both conventions are entirely stack-based and differ only in push order, on arm32 both pass the first four words in registers -- so there is no fast path to protect; and the owner's framing is the right one, ABI binds at boundaries and internals are free, which is only now expressible because the object writer created an export surface. NOT a deletion: the positional arms stay for the population that still needs them, and the intra-C call sites must move with the callee or bug-a-the-c-abi-gate-moved-the-callee-but-not-the-intra-c-call-sites returns. SEQUENCING: bug-a-i386-clobbers-ebx-across-a-cdecl-exported-function (p65) lands first or alongside. arm32/aarch64 stay unverifiable until they have a writer (p45). | — |
| decide-does-a-withdrawn-pin-leave-a-trace-and-is-its-version-number-reused | U | 60 | decide | RULED 2026-08-31 by the owner on the REUSE fork: DO NOT REUSE a withdrawn version number -- burn it. 'skipping is safe, there is nothing to gain with reusing apart confusion.' So no number ever names two binaries and "measured at v394" is always resolvable. The TRACE fork (erase vs annotate) was NOT separately ruled, and burning the counter forces it: a gap in the sequence is itself a question. Recommendation, explicitly not the owner's word: keep erasing from pin.log (what is in force), append a one-line withdrawn row to history.log (what was ever blessed) -- the two files already split those two questions. This also repairs a public claim: the launch fact sheet says pins are reconstructible, true of git and false of the ledger a reader would check. | — |
| decide-does-in-truncate-an-out-of-range-element-or-answer-false | U | 30 | decide | RULED 2026-09-01 (owner): option 1, KEEP FALSE. No code change — the current behaviour is the ruled one. The ruling came with a redefinition of the compat ceiling now in CLAUDE.md: on-par is on par with the LANGUAGE, not with FPC on inputs that are a presumed programmer error. ORIGINAL: q in [1,2,3] with q = 2^32+1: FPC 3.2.2 answers TRUE (it truncates the element), pxx answers FALSE (out of range is not a member). Both are now SELF-CONSISTENT -- the six-target disagreement is fixed and this is no longer a bug -- so this is purely which semantics we want. Recommendation: keep FALSE. Nothing is blocked on the answer. |
— |
| decide-does-nilpy-random-seed-itself-at-import | U | 60 | decide | RULED 2026-08-31 by the owner: OPTION 1 -- seed from entropy at import, keep random.seed(n) for determinism. We follow CPython; the upward-compatibility charter is one-directional and settles it. This OVERRULES the ticket's own recommendation of option 2 (a PXX_RANDOM_SEED env var): CPython has no such knob for random, and the debuggability it protects is not being relied on -- MEASURED, which is what made this cheap. Five NilPy tests reference random; THREE draw values (not two -- see the correction below) and all three seed explicitly and assert CONTRACTS not values (test_nilpy_math_surface_and_random.npy:4 says so in as many words). So entropy seeding breaks no test, and the stated cost lands on ad-hoc debugging, whose fix is the one line of random.seed(n) a CPython programmer already writes. Unblocks bug-b-nilpy-random-is-never-seeded-and-its-first-draw-is-the-low-bound. | — |
| decide-does-the-legacy-gtk-alias-still-point-at-gtk-2 | U | 50 | decide | RULED 2026-08-31 with its sibling decide-which-gtk-a-bare-gtk-gtk-h-means: the uses gtk alias moves to GTK 3. The owner named the deeper defect -- gtk and gtk3 are not parallel names: uses gtk and uses gtk3_c are C header imports resolved through the alias map, while lib/pcl/gtk3.pas is a PASCAL UNIT, so uses gtk3 finds a source file and not an alias. Renaming without fixing that just moves the confusion. FOUR files flip from GTK 2 to GTK 3, not three: test/test_c_gtk.pas, test_c_gtk_call.pas, test_c_gtk_types.pas AND test_c_gtk_window.pas -- this ticket's own body names all four and the ruling's list dropped one, the only one running a full gtk_main loop. Nothing else uses the alias. LANDED 2026-09-05 (frankC): uses gtk now takes the gtk-3 stem, all four build and link libgtk-3.so.0 and run green. The body's claim that the four "never touch GTK at runtime, they compile against test/my_gtk.h" is FALSE both ways -- three call into GTK, and my_gtk.h is an orphan nothing includes. |
— |
| decide-does-track-r-work-on-master-like-every-other-lane | U | 60 | decide | RULED 2026-08-31: option 1, and it was ALREADY EXECUTED -- origin/rust NO LONGER EXISTS. git ls-remote --heads origin returns dev, feat/cfront, feature/rust-frontend-skeleton, master, wasm and two wip/*, and no rust; last push to it was 2026-08-29 23:01. So options 2 and 3 (keep the branch) are moot, and Track R has in fact been committing to master all day. NOTHING IS STRANDED: cherry still reports 122 equivalent / 14 residual, but nine of the 14 are docs or ticket files and all four code-bearing ones were verified present on origin/master. Six of the 14 have now been checked (this ticket's two plus these four) and all six were patch-id false positives from rebasing. The tip 8937ef1f7 survives ONLY in frank-rust's local rust branch, whose upstream is deleted -- leave it: it costs nothing and deleting it is a one-way door on the last copy. origin/wasm (live, 6c88a2afc) does NOT inherit this verdict, per this ticket's own scope note. |
— |
| decide-dynamic-array-value-vs-reference-semantics | U | 55 | decide | dynamic arrays: pxx gives b := a VALUE semantics (a copy), FPC/Delphi give REFERENCE semantics (an alias) — is ours deliberate? | — |
| decide-env-write-side | U | 40 | decide | Policy: does pxx support WRITING the environment (setenv/putenv, os.environ[k]=v) — and does a write reach a child? | — |
| decide-esp-single-depth-division-into-a-declared-double | U+S | 45 | decide | Decide: on ESP targets, should x: Double; x := 1/3 compute at SINGLE depth? |
— |
| decide-esp-soc-axis-and-capability-table | U+S | 45 | decide | Decide: how does the compiler learn WHICH ESP chip, and what does it derive? | — |
| decide-finalize-noop-vs-refusal | U | 50 | decide | Finalize(x) is accepted and does nothing — deliberately, as a documented v1 shortcut. FPC empties the string; pxx leaves it intact. Refusing it is separable from implementing it and far cheaper, but would break code that currently compiles. Fork: refuse now, implement now, or leave the silent no-op. |
— |
| decide-float-fixed-output-exact-or-fpc-17-digit-cap | U | 45 | decide | writeln(d:0:1) of a huge double: pxx and CPython print the EXACT value (18446744073709551616.0), FPC caps at 17 significant digits and zero-pads (18446744073709552000.0). Which is pxx's rule? | bug-a-write-fixed-emits-false-digits-past-1e22 |
| decide-forin-mixed-int-float-ctor-vs-fpc | U | 20 | decide | for d in [1, 2.5] — FPC 3.2.2 prints 1.00 0.00, dropping the 2.5; pxx prints 1.00 2.50. Shipped as the correct answer rather than copied, because losing a written value is a defect and not a semantic choice. Confirm, or put FPC's answer behind --strict-fpc. |
— |
| decide-forwardlint-in-the-per-fix-loop | U | 60 | decide | decide: should forwardlint join the mandatory per-fix loop? |
— |
| decide-gate-line-convention | U | 60 | decide | Should ticket Gate: lines prescribe the long local suite, or the 40s native confirm plus Track T offload? Today they say the former while CLAUDE.md says the latter. | — |
| decide-gpc-as-corpus-target | U | 45 | decide | Track U: reject the GPC corpus wish, or keep it? Two sweeps have called it a rejection candidate. | — |
| decide-how-a-compiled-def-carries-its-signature-when-boxed | U | 88 | decide | A compiled NilPy def boxed into a variant is a bare CODE ADDRESS (VT_CALLABLE_TAG), carrying no arity and no defaults — so every call through a procedural value skips default-filling AND arity checking, and the ordinary callback shapes SIGSEGV. A lambda is correct because it takes the owned-callable path, which already carries ReqN..TotN. Fixing it means changing what a def-as-value OWNS, which the current representation deliberately avoids. That ownership call is the decision. |
— |
| decide-how-a-type-carries-an-identity-its-kind-cannot-hold | A | 55 | decide | "DECIDED AND BUILT 2026-09-06 (frankH): arm B, four slices -- ef518700b vehicle, 71b5bac58 renderers+operators, e06cfdeeb True-materialisation, 1e4852261 fields/params/return types/alias table. All carry sites done. Both consumer bugs closed. Encoding: one Integer, enum family numerically UNCHANGED (>= 0), every other family NEGATIVE so an unwidened >= 0 reader correctly sees not an enum -- that is what let the sites widen one at a time. The whose identity obligation was already met by NodeSemIdOf and EnumKindMatches and needed widening, not inventing. Original fork: DECIDED BY THE OWNER 2026-09-06: arm B, the side channel, even though it is the bigger overhaul. Some types have the LAYOUT of an existing TTypeKind and different SEMANTICS, and today only the kind survives a declaration, so the semantics are lost at the first registration boundary. Generalise the pattern an enum already uses (tyInteger PLUS SymEnumId) into an identity carried through symbols, record fields, params, return types and the alias table. THE RULE THAT FELL OUT AND IS WORTH MORE THAN THE CHOICE: if the WIDTH differs it needs a kind; if only the MEANING differs it needs a channel. Four in-tree precedents for B, including T = type Base (2f0ea073a), which is nothing BUT identity. ONE OBLIGATION, measured: a channel must answer WHOSE identity it is -- a bare is there an identity hands a set of TCol its element's member names. Unblocks bug-p-a-sized-boolean-is-true-and-not-true-at-the-same-time (p70) and bug-a-the-sized-booleans-render-as-a-digit-in-both-str-and-writeln. Argument, costings and precedents: devdocs/dev/type-identity-side-channel.md. |
— |
| decide-how-much-string-machinery-the-basic-frontend-gets | U | 35 | decide | String concat and comparison between BASIC variables need PXXStrConcat/PXXStrEq, whose bodies ship only in builtinheap, which a unit-free .bas never pulls — so PRINT s + t is a compiler-internal error. Every OTHER frontend solves this by pulling builtinheap unconditionally (a Pascal hello-world is 63 KB). BASIC's unit-free path is 559 bytes. The fork is size vs capability, and it is a product call, not a code one. |
— |
| decide-how-python-shaped-shims-should-be-shipped | U | 70 | decide | A shim whose content is Python-level aliases (six: text_type = str) cannot be written in the mimic_<name>.pas slot, and the working alternative — a NilPy .py in a library root — silently defeats --no-shims. Three options, recommendation is to let the shim lookup also probe mimic_<name>.py. |
— |
| decide-how-the-sys-intrinsics-reach-wasi-when-the-compiler-links-no-pal | U | 70 | decide | 24 of wasm32's 52 remaining compiler.pas refusals are the sys* intrinsics (tkSysOpen 15, tkSyswrite 6, tkArgStr 3), and they collapse to ONE blocked primitive: opening a file under WASI. That needs preopen resolution, rights computation and errno mapping, which exist once in lib/rtl/platform/wasi/platform_backend.pas -- a unit compiler.pas deliberately does not link, because the compiler bootstraps on intrinsics to avoid an RTL dependency. Three ways out, each with a real cost: duplicate the capability model into builtinheap.pas, link the PAL into the compiler, or factor the WASI helpers into a shared include. The choice spans Track A and Track B files, so it is not the wasm lane's to make. | — |
| decide-install-qemu-system-and-a-freebsd-image-on-plexus | U | 55 | decide | APPROVED 2026-08-31 by the owner: install qemu-system and pull a FreeBSD image on plexus. 'this box is dedicated to development. i think we have plenty disk space left. so yes, we can pull a BSD image.' Verified at ruling time: plexus root filesystem 156G, 84G available (44% used), so a multi-GB image is comfortable. This unblocks the FreeBSD port work that had no bootable kernel to test against. Fleet context recorded the same day and NOT part of this approval: borg is being repaired this week, and seven is moving off-site to a work location because it is too noisy for the house -- so plexus is the box to build this on, not seven. | — |
| decide-int-div-zero-behavior-unification | A | 43 | decide | DECIDE: unify integer div/mod-by-zero behavior across targets | — |
| decide-interface-members-in-aggregates-lock-strategy | U | 60 | decide | SIX open Track A tickets (two of them use-after-frees) are all the same missing capability: a COM interface held inside an aggregate is invisible to every container-level retain/release walk. The one fix is blocked on a heap-lock question that was attempted once and reverted. Which strategy — reentrant lock, unlocked interface pass, or a copy-site-only stopgap — and who validates it against the threading stress tests? | — |
| decide-ipv6-dualstack-and-aaaa-ordering | U | 40 | decide | Policy: IPV6_V6ONLY on a :: listener, and which address wins when a host has both A and AAAA | — |
| decide-is-the-2026-07-12-esp-park-still-in-force | U+S | 65 | decide | DECIDE: is the 2026-07-12 ESP park still in force? 23 ranked tickets and a staffed agent depend on it | — |
| decide-may-a-lane-be-given-the-full-suite-escape-for-four-corpus-builds | U | 55 | decide | RESOLVED 2026-08-31 as a MISUNDERSTANDING OF INTENT -- and the misunderstanding was CLAUDE.md's, not the agent's. Answer: YES, and no permission was ever needed. PXX_ALLOW_FULL_SUITE=1 is a SPEED GUARDRAIL, not a permission gate; the owner: 'those 10 minutes were exactly the issue, because sometimes agents run that full 10 minute test for every byte they edit, which is wasteful... but agents should be able to override it autonomously.' The doc said otherwise in two places and contradicted its own reversibility test three paragraphs earlier -- running tests is reversible, unlike sudo/hardware/money which it was listed beside. CLAUDE.md and the hook refusal text were both corrected in the same commit, because closing this ticket without that fixes one instance of a rule that would keep firing. Unblocks feature-c-import-a-pascal-unit-under-a-mangled-name section 6 (four corpus builds + one deletion, an afternoon). frankwasm was RIGHT to park rather than reshape a denied command. | — |
| decide-may-agents-fetch-thirdparty-sources-as-oracles | U | 50 | decide | Several ranked Track B tickets need a third-party package present on the box — as a differential oracle (reportlab) or as the compile target itself (html5lib, tinycss2, webencodings). None is installed here and fetching one is a supply-chain action, so the lane is stalled on a policy answer, not on work | — |
| decide-may-uses-math-cost-the-heap-and-exception-runtime | U | 30 | decide | Making Floor/Ceil raise EInvalidOp like FPC needs uses sysutils in math's implementation — measured, that is the ONLY way to raise anything, since Exception is not visible otherwise. That makes every uses math program require the heap + exception runtime: test/test_math.pas stops compiling today. Fork: pay it (and fix the prescan in A), saturate silently, or leave the wrong values. Blocks bug-b-floor-of-an-out-of-range-double-returns-0-where-fpc-raises. |
— |
| decide-merge-variant-c-with-bare-name-collision | U | 75 | decide | Variant C (sibling Exception classes) is BUILT and GREEN on wip/exception-sibling-design. Merging it makes both sysutils and pylib export a class named Exception, and pxx resolves such a collision to the FIRST unit named where FPC resolves it to the LAST. Ship now and accept a backwards bare-name answer for programs using both units, ship behind the parity fix, or change the tests' contract. Recommendation inside. |
— |
| decide-nilpy-and-or-return-operand-or-bool | U | 40 | decide | decide: should NilPy's and / or return an OPERAND, as Python does? |
— |
| decide-nilpy-arithmetic-dunder-scope | U | 60 | decide | Decide: how far does NilPy follow Python's arithmetic/ordering dunder protocol? | — |
| decide-nilpy-bigint-vs-64bit-cells | U | 40 | decide | decide: NilPy integer semantics — arbitrary precision vs 64-bit (uforth needs one) | — |
| decide-nilpy-builtin-keyword-only-parameters | U | 40 | decide | Should NilPy builtins enforce Python's KEYWORD-ONLY parameters? | — |
| decide-nilpy-builtin-vs-pascal-unit-name-resolution | U | 45 | decide | Settled by the governing rule (user, 2026-08-13): the DEFAULT follows the reference implementation per frontend — CPython for .npy, FPC for .pas — deviations behind --strict-*. So shadowing is ALLOWED and PREFERRED, reserved needs the bar 'principally unsolvable', and the tier is a compatibility statement rather than a convenience. What is left to decide: the marker's spelling, whether print stops being a token, and whether a --strict-python peer is wanted. |
— |
| decide-nilpy-class-as-value-dispatch-strategy | U | 5 | decide | A variant tag cannot make cls(...) callable — NilPy ctor params are statically INFERRED per class, so two classes of the same arity have different ABIs. Choose: compile-time candidate dispatch, an RTTI-driven runtime marshaller, or a uniform variant ctor ABI for classes used as values. |
— |
| decide-nilpy-class-attribute-instance-read-model | U | 65 | decide | How should inst.attr read a CLASS attribute? Full Python fall-through with per-instance overrides (correct, invasive), or a whole-program static specialisation using the PyDynAttrEverAssigned-style scan already in the frontend (cheaper, correct for programs that never override per instance)? Blocks bug-nilpy-class-attribute-unreachable-through-the-class-name. |
— |
| decide-nilpy-classmethod-cls-binding | U | 40 | decide | @classmethod is refused by name. The machinery is closer than its ticket says — @staticmethod already injects a hidden $clsrecv at slot 0 and the dispatch already passes A class there — so the only open question is WHICH class that is at run time for an inherited method reached through an instance, and whether a cls that is the statically-known class is acceptable or must be refused until it is the runtime one. |
— |
| decide-nilpy-closure-model | A | 50 | decide | — | |
| decide-nilpy-dict-mutation-during-iteration | U | 35 | decide | Raise on dict mutation during iteration, or keep the snapshot? | — |
| decide-nilpy-dunder-file-for-a-compiled-program | U | 55 | decide | What should file be in a COMPILED NilPy program? Today it is argv[0] (the binary), CPython says the source path. The idiom that cares is os.path.dirname(os.path.abspath(__file__)) to find data files next to the script — it is how uforth locates STD.UFO, and it fails today when the binary and the sources live in different directories. |
— |
| decide-nilpy-eager-map-filter-reversed-enumerate | U | 55 | decide | map/filter/reversed/enumerate return LISTS, not lazy iterators. MEASURED: for v in map(risky, xs) with an early break raises an exception CPython never reaches (f runs 1000x vs 4x), so a working CPython program crashes — this is an upward-compatibility break, not a perf note. Decide: fuse at the for-loop consumption site (recommended), full iterator protocol, or document |
— |
| decide-nilpy-eval-at-runtime | U | 35 | decide | Does NilPy support eval(s) / exec(s) over a runtime string at all? A compiled dialect either ships a parser in every binary or it does not — this is a design call, not work, and it has sat as a to-do row on a bug ticket for three sessions. |
— |
| decide-nilpy-gui-tk-vs-pcl | A | 25 | decide | RESOLVED 2026-07-21: keep the real Tcl/Tk embed on Linux (works); Windows = opt-in tk emulate/wrap via a platform include, later. Follow-up: feature-pcl-tk-windows-compat | — |
| decide-nilpy-hasattr-per-instance-semantics | U | 35 | decide | decide: should NilPy's hasattr answer per-INSTANCE or per-CLASS? | — |
| decide-nilpy-import-rule-vs-a-cpyext-extension-module | U | 75 | decide | The NilPy import rule made import hello_ext — CPython's own spelling for a C extension — unspellable, and six cpyext jobs went red |
— |
| decide-nilpy-imports-that-collide-with-a-pascal-rtl-unit | U | 60 | decide | Eight lib/rtl Pascal units share a name with a Python stdlib module (classes io json math random re strings types). A NilPy from types import X silently binds to Pascal's types.pas; from classes import X fails inside Pascal's classes.pas with a message about Delete. What should a NilPy import do when the name resolves to a unit that is not a NilPy module? |
— |
| decide-nilpy-int-promotion-costs-10x-on-ordinary-loops | U | 60 | decide | Option 1 was decided without a number; the number is 10x | — |
| decide-nilpy-int-promotion-default | U | 60 | decide | Decide: should NilPy int bindings default to promotable, not native int64? |
— |
| decide-nilpy-mixed-type-operand-policy | U | 60 | decide | Decide: what should NilPy do when an operator gets operand types Python rejects? | — |
| decide-nilpy-multiple-inheritance-c3-or-delegate | U | 40 | decide | DECIDED 2026-08-08: multiple inheritance is FLATTENED at compile time. The first base with no body in this file (or the first base) is the real parent; every other base's own body is replayed into the derived class, never its ancestors. Divergences from CPython: isinstance/except do not see a flattened base unless its class is recorded as one at run time, super() across a flattened body is refused, and a diamond is refused where flattening could drop or duplicate an ancestor. A diamond whose flattened mixins have every ancestor in the real parent chain drops and duplicates nothing, and the exception idiom class E(LocalError, FileNotFoundError) is that shape. |
— |
| decide-nilpy-none-str-representation | U | 45 | decide | \"\" is None is True for a statically str-typed value and False for the same string in a variant — the variant path ALREADY models None-vs-empty correctly, so choose: route str Optionals through variants, give None-str a distinguished non-nil handle, or leave the divergence documented |
— |
| decide-nilpy-none-str-sentinel-vs-textstr-kind | U | 40 | decide | Re-ask of decide-nilpy-none-str-representation: the chosen fix (a NilPy string kind whose blocks may be zero length) rests on a block kind that nothing in the tree ever stamps, so it is a Track A representation project rather than a bugfix. A None SENTINEL closes the reported bug at a fraction of the cost — but closes less. | — |
| decide-nilpy-object-dict-key-hashing | U | 40 | decide | A class with eq and no hash is unhashable in CPython, so d[V(1)] = x raises. NilPy stores it and then never finds it again — data in, nothing out, silently. Refuse the store (faithful), make content lookup work (friendlier, needs a hash story), or document the divergence. The ticket that found it says explicitly to decide rather than guess. |
— |
| decide-nilpy-optional-int-none-vs-zero | U | 60 | decide | decide: NilPy Optional[int] — None must be distinct from 0 | — |
| decide-nilpy-parallel-capture-semantics | U | 5 | decide | DECIDE: NilPy parallel for-in capture model — what's private, what's shared, how reductions read | — |
| decide-nilpy-runtime-dunder-dispatch-mechanism | U | 45 | decide | Decide: how should NilPy dispatch dunders on an instance whose class is known only at RUN time (container elements)? | decide-nilpy-runtime-dunder-dispatch-strategy |
| decide-nilpy-runtime-dunder-dispatch-strategy | U | 45 | decide | Decide: how should NilPy dispatch dunders on a Variant-held instance? | — |
| decide-nilpy-set-as-a-distinct-type-or-a-list | U | 55 | decide | pxx backs a Python set with TPyList. That makes set difference work, makes list - list unrejectable, and makes a set repr as [1, 3] instead of {1, 3}. Give sets their own row, or keep the alias and pay at run time? |
— |
| decide-nilpy-str-is-bytes-or-codepoints | U | 55 | decide | NilPy strings are BYTES where CPython's are code points: len('héllo')==6, s[1] is half a character, and s[::-1] silently produces invalid UTF-8. Decide the target — full code-point str, UTF-8-aware indexing over the byte buffer, or a documented ASCII-only limit | — |
| decide-nilpy-transitive-nested-def-capture | U | 40 | decide | decide: NilPy transitive capture for sibling nested-def calls | — |
| decide-nilpy-what-version-does-sys-version-info-claim | U | 62 | decide | sys.version_info is absent, and providing it is a product claim, not an implementation detail: real code branches on it to select code paths, so any number we answer silently steers third-party libraries. Decide what version a NilPy build reports — and whether it reports a CPython version at all. | — |
| decide-nilpy-where-the-exact-decimal-float-core-lives | U | 60 | decide | NilPy's float repr needs exact decimal digits + a correctly-rounded strtod. Both exist, in lib/rtl/sysutils.pas — which a BUILTIN unit may not use (builtins sit below the Track B libraries, and pylib dragging sysutils in would link it into every NilPy program). Move the core down into a builtin unit, duplicate it, or relax the layering? Blocks bug-nilpy-float-repr-is-not-pythons-shortest-roundtrip. | — |
| decide-old-style-object-types | U | 30 | decide | Decide: do we implement Turbo Pascal object types? |
— |
| decide-one-answer-to-have-i-already-compiled-this-unit | U | 40 | decide | Three tickets in three lanes are all 'a compilation unit got processed twice', served by three unrelated mechanisms: unit-NAME keying (Pascal/NilPy), an @cpath: key space (path-form C units), and preprocessor include-guard visibility (C headers). Two is a smell, three is a design flaw. Question for the user: does 'have I already compiled this translation unit?' deserve ONE answer, or are three correct-in-their-own-lane answers the right shape? | — |
| decide-one-managed-string-kind-with-an-element-width-or-a-second-kind | U | 60 | decide | RULED 2026-08-31 (owner): option B, one managed-string kind carrying an element width. Ruled BY CONSTRUCTION — the measurement that was in flight came back YES (ASTStrElemTk exists at defs.inc:4495 with 27 readers, plus ProcRetStrElemTk/UFldPtrElemStrTk follow-ons), tyWideString has zero references left, and pasparser_decl.inc:464 already broke the alias the B way. The 636-site audit never has to happen. Carried forward: the runtime header ALREADY reserves an encoding enum (PXX_ENC_BYTES/UTF8/UCS2/UCS4 at builtinheap.pas:289) that nothing stamps or reads — see feature-a-stamp-and-read-the-managed-string-encoding-field. | — |
| decide-operator-table-keyed-on-one-operand-or-two | U | 40 | decide | Decide: should the operator-overload table be keyed on BOTH operand types? | — |
| decide-own-language-first-name-resolution | U | 5 | decide | the user's 'own language first' rule (own-language declarations beat cross-language matches, outranking import order) is stated but not specified — settle the exact rule before anyone implements it | — |
| decide-own-language-first-vs-explicit-import-in-a-case-insensitive-language | U | 60 | decide | Own-language-first was decided with explicit import as its safety valve — 'nothing becomes unreachable, it just has to be asked for by name'. Measured: in Pascal there IS no distinct name to ask with, because Pascal is case-insensitive, so uses './math.c' does not ADD exp alongside Exp, it REPLACES it. The rule cannot be both a hard precedence and overridable by explicit import. Pick which gives. |
— |
| decide-parenless-all-defaulted-routine-in-argument-position | U | 40 | decide | Bare F in ARGUMENT position where F is a routine with all-defaulted parameters: call it, or take it as a procedural reference? Statement and expression positions now call it; argument position is genuinely ambiguous and was deliberately left unfixed. |
— |
| decide-pascal-uses-campaign-scope | U | 55 | decide | Decide: how should the uses-is-transitive fix be scoped and sequenced? |
— |
| decide-pchar-node-side-storage-or-a-pchar-type-kind | U | 40 | decide | The last thing owed by refactor-centralize-managed-string-pchar-conversion is slice 3, and its premise expired twice. WideChar got a real type kind (tyWideChar) and no longer wants node-side storage; PChar cannot copy that, because a PChar's pointee VARIES and a kind per pointee does not scale. Meanwhile the deref walk is now ONE function and 198/198 cross-product rows match fpc, so the third option — do nothing structural and keep extending the one walk — is live. This is a design call, not work. | — |
| decide-pcl-may-use-pylib | U | 55 | decide | decide: may a PCL library unit use pylib (Python runtime types) to accept Python-shaped arguments? | — |
| decide-pointer-difference-unit | U | 30 | decide | FPC's p - q answers BYTES when either operand is an untyped Pointer (which includes @x under the default {$TYPEDADDRESS OFF}) and ELEMENTS when both are the same typed pointer. pxx always answers elements. p - @a[0] therefore prints 8 in FPC and 2 in pxx — a silent difference in ported code. Match FPC, keep the uniform rule, or diagnose? |
— |
| decide-progress-should-decide-slugs-auto-tag-track-u | U | 30 | decide | Should decide-* slugs auto-tag Track U in the ranker? |
— |
| decide-promoint-rvalue-representation | U | 85 | decide | Promotable int: what IS an rvalue once heap bignums exist? | — |
| decide-pxx-thread-local-storage-is-gs-relative-and-the-x86-64-psabi-is-fs-relative | U | 55 | decide | pxx installs its per-thread block on GS (thread_emit.inc:142, GS, not fs: fs belongs to libc, and a pxx program may link one) and the x86-64 psABI puts ELF TLS on FS. Measured 2026-09-06: a pxx-native thread gets a DISTINCT non-zero GS base (main 42D110 in .bss, child 7BDB21FF7A80 off its own stack) with FS 0 in both, so the mechanism works -- for pxx-compiled code. The fork is what happens at the boundary: GS-relative thread-locals cannot be reached by TLS relocations in a gcc-built object, and --emit-obj exists precisely to be linked into foreign programs. Nobody has ruled on this and bug-c-__thread-is-accepted-and-silently-ignored-so-thread-local-storage-is-shared cannot be implemented without the ruling. A second measured fact bears on it: a thread pxx did NOT create inherits the parent's GS base (glibc pthread_create gives main and child the identical 4298F0), so any GS scheme is also deciding what a foreign-created thread gets. |
— |
| decide-pxxpdf-ticket-obsolete | U | 50 | decide | Close feature-lib-pxxpdf-reportlab-compat as obsolete, or keep it? |
— |
| decide-pyeval-bignum-strategy | U | 40 | decide | decide: how should pyeval handle arbitrary-precision (bignum) integers? | — |
| decide-pylib-exception-vs-sysutils-exception | U | 55 | decide | pylib and sysutils both declare a class named Exception and the name is deliberately shared program-wide, so except Exception: catches either RTL's raise. The cost, measured: under uses sysutils, pylib pylib's OWN classes bind their ancestor to SYSUTILS' Exception, so pylib can never add a member sysutils lacks — which killed e.args after it had shipped. Decide who owns Exception before anything else is built on it. |
— |
| decide-re-pin-after-the-dynarray-aliasing-flip | U | 70 | decide | The dyn-array aliasing flip (937c51dc2) is a codegen change, so gate.sh quick reads RED on its pinned-seeded fixedpoint step for EVERY lane until pinned is refreshed. Re-pin now, or wait for T's full matrix? |
— |
| decide-reduced-compiler-switch-spelling | U | 55 | decide | How does a reduced build get selected — subtractive (omit-c), positive-list (only-pascal), or a named-configuration file? And do frontend and target selection compose freely or only in blessed combinations? The user flagged the names in the parent ticket as placeholders. Recommendation: subtractive defines as the mechanism, named configurations as the tested surface. |
— |
| decide-reprice-nilpy-ast-typing-module-scope | U | 55 | decide | feature-n-nilpy-ast-typing-module-scope sits at prio 55 — top of the ranked Track N queue after the META — but its own 2026-08-09 note concludes it is now an OPTIMISATION, not a correctness item, and asks to be re-priced. prio is the user's field, so: re-price, or leave it steering the queue? | — |
| decide-revisit-object-types-rtl-generics-fired-the-trigger | U | 70 | decide | decide-old-style-object-types chose option A (do not implement) with an explicit revisit trigger: 'the moment actual source someone wants to build needs it. Not an FPC test — a program.' generics.collections.pas needs it, which blocks rung 6 of feature-pascal-corpus-expansion (prio 75). But the measurement changes the cost case: the corpus contains exactly ONE = object, it has no fields, no inheritance, no virtual methods and no constructor, and the equivalent generic record-with-methods compiles and runs on HEAD today. The decision's cost analysis — a second object model with different storage, lifetime, assignment and VMT — does not apply to the thing actually blocking us. |
— |
| decide-riscv64-vs-the-bug-queue-for-autonomous-nights | U | 50 | decide | feature-a-riscv64-as-a-hosted-first-class-target is the top-ranked Track A ticket at prio 50, and its own log says it was ranked 'as a strategic target rather than an urgent one'. It is a multi-session job. An unattended overnight Track A session keeps reaching it, skipping it, and taking a p40 bug instead — which may be right, but it is a decision being made silently every night. Make it once, out loud. | — |
| decide-rtl-math-correctly-rounded-vs-fast-tier | U | 35 | decide | lib/rtl/math.pas's transcendentals are correctly rounded and ~1000x slower than libm — MEASURED: Ln+Exp 16,480 ms per 1M pairs against glibc's 13 ms, and the new dd Sin/Cos 29,383 ms against a plain-double 673 ms. That is the SHIPPED standard today, not a proposal. Question: is one correctly-rounded tier the intended answer for a language whose demos draw graphics, or does the RTL want a fast tier alongside it? Three options, recommendation inside. | — |
| decide-rtti-kind-numbering | U | 40 | decide | typinfo.pas declares TTypeKind in FPC's order (tkInt64=19) but the RTTI blob the compiler emits carries the COMPILER's TTypeKind (tyInt64=13), so if mi^.RetKind = Ord(tkInt64) is silently false. Three ways out; they differ in whether the RTTI blob's numbering — a compiler ABI — changes. Recommendation: option 2. Needs a human call because option 1 breaks already-compiled consumers and option 2 spends the FPC-compatibility argument the FPC-ordered enum was added for. |
— |
| decide-rtti-none-semantics | A | 40 | decide | decide: --rtti=none semantics — what happens to the FUNCTIONAL parts of the RTTI blob? |
— |
| decide-runtime-primitive-layering | U | 70 | decide | Where does a runtime primitive live? — DECIDED: a PAL per language | — |
| decide-scope-hiding-vs-flat-overload-set | U | 60 | decide | One rule explains four separate symptoms: a declaration should HIDE a same-named one from an outer/earlier scope unless marked overload. pxx behaves as if everything were overload — one flat set, first-in-chain wins. Decide whether to adopt hiding, and which marker carries it: any {$mode}, --strict-overload/{$MIMIC FPC}, or the default |
— |
| decide-set-vs-array-of-const-at-the-same-overload-slot | U | 30 | decide | DECIDED 2026-08-16 (user): leave it — don't overload on a set and an array of const at one slot, give the function a decisive name; a cast-style (set)[...] was considered and rejected as non-standard Pascal. Docs footnote only. The separate bug-p-set-literal-elements-are-not-type-checked STAYS OPEN and is the real defect. Background: RE-SCOPED 2026-08-16 after re-measurement: the original table was wrong (pxx is NOT order-independent — it flips on all four bracket shapes, FPC only on the genuinely ambiguous one). Most of the difference is bug-p-set-literal-elements-are-not-type-checked, filed separately; fix that and content disambiguates as it does in FPC. What is left to decide is the true tie only: [dTue], [dMon, dWed], []. |
— |
| decide-settextbuf-needs-buffered-text-io-or-stays-missing | U | 55 | decide | RULED 2026-08-31 (owner): option (a) — implement it. The ticket's premise is STALE: lib/rtl/textfile.pas already buffers reads at 4096 bytes inline in the Text record, so the job is not 'build buffering', it is one shape change (inline array -> BufPtr/BufSize) after which SetTextBuf is FPC's literal four assignments. Ruled with it: buffer writes too, but take C99 7.19.3p7's buffering POLICY rather than FPC's, order cross-RTL writes with a flush registry, and make lib/crtl's setvbuf real (it is a stub returning success today). Implementation: feature-b-buffered-text-io-and-settextbuf and feature-c-crtl-stdio-buffering-and-setvbuf. | — |
| decide-shift-operator-promotion-width | U | 45 | decide | Decide: what width do shl / shr happen at for a 32-bit operand? |
— |
| decide-should-a-c-main-exist-on-the-esp-profile-at-all | S | 35 | decide | A C program with a main refuses on xtensa's DEFAULT and BARE profiles: C program entry stub on xtensa: hosted linux only. On the ESP profile there is no argc on the stack to pass to main and no kernel to take the exit_group that ends it. That is a DELIBERATE guard and a correct statement, and the question it leaves open is a design one, not a bug: should a C main exist on a profile where FreeRTOS gives tasks rather than processes, and if so what does it mean? Split out of bug-c-including-stdio-h-refuses-to-compile-for-xtensa, whose measurable claim is now false — the posix profile compiles #include <stdio.h> plus main at 660016 B with NO --xtensa-long-calls and RUNS. Filed against frankS as the residual owner: this is the ESP profile's semantics, not the C frontend's. |
— |
| decide-should-a-null-variant-raise-like-fpc | U | 25 | decide | pxx spells FPC's Null and Unassigned with ONE tag (VT_EMPTY). fpc 3.2.2 prints/casts an Unassigned as the empty string but RAISES EVariantTypeCastError for a Null, in both string(v) and WriteLn(v). Rendering now follows the Unassigned half, which is the only answer one tag can give. Adopting the raise means either a second tag or making Null and Unassigned both die -- a language call, not a bug fix. |
— |
| decide-should-an-open-array-parameter-become-a-two-word-descriptor | U | 55 | decide | DECIDED 2026-09-03 BY THE OWNER: ARM A. "if we need more meta info, use more data fields. hardly a decision." Carry the metadata; do not spend correctness to keep one word. The recommendation in this body was arm C and it is SUPERSEDED -- left below unedited because the reasoning for C is the reasoning a future reader will re-derive, and it should be visible that it was heard and overruled. ONE SUB-QUESTION REMAINS AND IT DECIDES WEEKS VS DAYS: this body asserts arm A is a wire-format change because [ptr-8] is SHARED with dyn arrays and AnsiString handles -- but passing an open-array PARAMETER as two words at the CALL BOUNDARY may not require changing the storage convention those two rely on. Nobody has measured that. Measure it before touching 633 sites. SEQUENCING: not to be started while the phase-4 flip is unreleased -- both serialise the backends. Original text follows. THREE SESSIONS HAVE NOW STOPPED AT THE SAME WALL and each stop looked like the ticket being hard rather than mis-filed, so nobody escalated. bug-a-address-of-an-open-array-element-points-at-the-marshalling-temp is not a bug someone can attempt: its own body says the fix is a representation change across 633 IsArray sites in 27 files and 6 backends, and that the only cheaper arm is impossible for a record field or a 2-D row. That is a design fork and it belongs here. THE FORK: pxx passes an open array as ONE word -- a pointer whose length sits at [ptr-8], the same convention AnsiString handles and dynamic arrays share -- so an argument that already carries that header is passed by reference and one that does not (a static array, a record field, a 2-D row) must be copied into an adjacent-header temp. FPC passes TWO words, (pointer, high), and therefore aliases everything. The temp is a faithful, writable, correctly-strided view whose writes are copied back on return, so element access, write-through, Length and High are all correct; THE ONLY OBSERVABLE IS AN ADDRESS THAT ESCAPES THE CALL. My recommendation is arm C: keep the one-word convention, record the divergence as chosen, and revisit only on real source that needs the address to outlive the call -- nobody has produced any. Deciding this closes the bug ticket one way or the other; leaving it open at p55 guarantees a fourth session reads the same summary and stops in the same place. |
— |
| decide-should-forwardlint-join-the-mandatory-per-fix-loop | U | 55 | decide | Collapses the two tickets that both asked what may join the three-line per-fix loop. make compiler/pascal26 compiles compiler.pas WITH pxx, so the loop and the self-host fixedpoint are blind BY CONSTRUCTION to a construct pxx accepts and FPC rejects -- and FPC is the bootstrap seed. tools/forwardlint.py models FPC's resolution, runs in 4.1s, and as of 7aba316be is silent on a clean tree, which removes the one argument that kept it out. Five measured instances of the seed breaking while the loop stayed green. Recommendation: option 1, narrowly. The edit is to CLAUDE.md's gating section, so only the owner makes it. | — |
| decide-should-forwardlint-run-in-the-build-not-only-the-gate | U | 55 | decide | Should forwardlint run in make compiler/pascal26, not only in gate.sh? |
— |
| decide-should-from-accept-a-quoted-foreign-file | U | 45 | decide | A bare NilPy import resolves to Python only, and the escape is import 'x.pas' as x. There is no matching escape for from x import Name -- from 'x.pas' import Name is refused with "expected a module name after from". Decide whether the quoted form should be accepted after from, or whether the alias form is deliberately the only door. A test already lost an assertion to this. |
— |
| decide-should-the-gate-prove-self-compile-at-more-than-one-o-level | U | 55 | decide | A -O0-only self-compile failure passed the per-fix gate, the self-host fixedpoint, and every Track T tier — the class is structurally invisible. Widening the gate would catch it and would also lengthen the loop CLAUDE.md is emphatic about NOT widening. Genuine fork; coordinator must not settle gating policy. | — |
| decide-should-unreachable-code-that-breaks-the-LOAD-be-pruned-at-O0 | U | 65 | decide | RULED 2026-08-31 by the owner: PRUNE, at every level including -O0, following the de-facto standard the way FPC does. Measured against three independent implementations (gcc 13.3 + 15.2, clang 21.1.8, tcc 0.9.27, x86-64 Linux): ALL prune the busybox xatonum.h idiom at every level, tcc included -- and tcc has no optimizer, so folding a syntactically-constant condition and dropping statements after a return is LOWERING, not optimization. -O0's byte-identity charter is therefore not spent by doing it, which is what the original three options all assumed. Outside that consensus core the three disagree in three different ways, so nothing portable relies on it and we are free to diagnose. HARD CONSTRAINT, measured: a dead arm containing a label whose ADDRESS IS TAKEN is kept by all three at every level -- the rule is 'unreachable AND address does not escape'. True source-1:1 becomes a NAMED FLAG (owner's spelling: -OO), never a level, per decide-the-o-level-charter. C99 6.9p5 makes the construct UB, so pruning and rejecting both conform: this is a choice, not obedience. Implementation: feature-a-fold-the-consensus-dead-branch-core-at-every-level. | — |
| decide-sole-a-guard-for-unattended-sessions | U | 55 | decide | How should an UNATTENDED session satisfy the sole-A guard? | — |
| decide-staff-track-c-to-unblock-own-language-first | U | 50 | decide | bug-c-definition-of-an-intrinsic-name-overwrites-the-pascal-routine (C, p55) is the only thing blocking feature-a-own-language-first-symbol-resolution, and Track C is unstaffed. Staff it, fold it into an existing session, or leave the chain parked? | — |
| decide-stralloc-one-implementation-or-fpcs-two | U | 35 | decide | FPC ships two incompatible StrAllocs — strings allocates prefix-free, sysutils allocates with a 4-byte size prefix — and uses SysUtils, Strings silently pairs the first with the second's StrBufSize/StrDispose, which is a measured heap error. We shipped ONE implementation (the prefixed one, shared by both units). Confirm, or ask for FPC's two-implementation split reproduced warts and all. |
— |
| decide-t-notification-transport-poll-not-webhooks | U | 60 | decide | How Track T's findings reach an agent or a human: polling, never webhooks. 60s is the baseline; adaptive backoff is allowed but the daemon must not grow a time-based one. | — |
| decide-t-queue-scope-2026-08-03 | T | 60 | decide | User calls on four standing assumptions in the Track T queue: borg's watcher, the arm oracles, who may pin, and when the NilPy fuzzer earns its keep | — |
| decide-the-licensing-page-says-no-license-yet-and-the-repo-has-one | U | 60 | decide | DECIDE: licensing-concerns.md says "No License Yet"; the repo root carries LICENSE |
— |
| decide-the-o-level-charter | U | 65 | decide | RULED 2026-08-30, amended the same day, and again 2026-09-02 when source 1:1 LEFT O0 for the named flag -OO (O0 now does the lowering gcc/clang/tcc all do with no optimiser asked for; 1:1 stayed load-bearing for telling a lowering bug from an optimizer bug, so it moved rather than being violated). O0 zero optimization / O1 DEBUG-SAFE optimization (our divergence: this is -Og elsewhere, unenforced until someone builds the test) / O2 proven default / O3 experimental, on track for O2 / O4 RESEARCH — correct but speculative, may never promote, and its purpose is to keep O3's drain honest. The rejected idea was O4 as the TRADE-OFF bin: 'only for certain applications' is a different axis, and those stay NAMED FLAGS because an author must choose WHICH trade, not HOW MUCH. O4 is swept on a slower cadence than the ladder, because nothing depends on it. |
— |
| decide-the-o3-tier-is-34-percent-faster-and-nothing-gates-it | U | 65 | decide | -O3 was 28-34% faster than -O2 on the compiler's own workload. ~78-82% of that was ONE pass -- EmitStaticLitHandle / EmitStaticLitHandleA64, the static string-literal handle -- PROMOTED to -O2 in 440c822e6a80 (both backends; quick gate green, full+cross sweep requested from frankT, not pin-eligible until it returns). MEASURED AFTER: the remaining -O3 gap is ~5-7%, real (7 of 9 paired runs) but at the edge of what a contended box resolves. The campaign is effectively over -- the rest does not justify per-pass promotion at this measurement precision. -O1 limbo untouched. | — |
| decide-the-ticket-lock-is-too-heavy-for-a-per-minute-commit-loop | U | 70 | decide | The ticket lock is too heavy for the loop it sits in — 607 commits, 3 locks | — |
| decide-the-wasm-umbrella-at-70-reinstates-everything-the-owner-demoted-to-25 | U | 60 | decide | RULED 2026-09-01 (owner): option (a), and wider than asked -- LINUX ONLY FOR NOW, so BSD is demoted alongside wasm. umbrella-wasm-is-a-real-platform 70->25, umbrella-pxx-hosted-beyond-linux 85->25, and the three BSD leaves that carried their own higher numbers (feature-port-openbsd-libc 50, decide-openbsd-pinsyscalls 45, feature-port-freebsd-native 55) all to 25. Recorded in devdocs/dev/the-goal-cross-cross.md under CURRENT FOCUS, above the matrix, so the next umbrella is priced from the ruling rather than from the platform list that caused this. ORIGINAL: On 2026-08-30 the owner ruled WASM IS LOW PRIO FROM NOW ON -- 'these tickets stay OPEN and correct; they simply must not outrank ordinary Track A work' -- and the wasm bugs were correctly set to prio: 25. On 2026-08-31, 8d9a5794b created umbrella-wasm-is-a-real-platform at prio 70. effective_prio takes the max over dependents, so those same tickets come back out of ready at 70 and DO outrank ordinary Track A work: exactly the outcome the ruling forbade. The leaves were re-priced; the goal above them was not. The ranker is working perfectly and delivering the opposite of the instruction. One number fixes it, but which number is the owner's call. NOT affected: bug-a-managed-locals-leak-on-an-unwind-on-wasm32-and-xtensa, whose 75 comes from umbrella-managed-memory-is-correct and is legitimate under either reading. |
— |
| decide-threadsafe-gate-is-reach-based-not-use-based | U | 45 | decide | Putting TThread in Classes where FPC code looks for it is not a size trade-off — MEASURED, it makes every uses classes program require --threadsafe, because the gate fires on REACHING __pxxclone's unit rather than on calling it. Same wall the palfutex split just removed one level down, but splitting cannot fix this one |
— |
| decide-tobject-classinfo-blob-or-refusal | U | 42 | decide | TObject.ClassInfo is the last member of feature-pascal-builtin-tobject-class still PXX-REJECT, and it is a judgment call, not an implementation choice: our RTTI blob is honest for identity comparison and wrong for anything that walks FPC's TTypeInfo layout. Answer or refuse — the third option is to answer and be silently wrong for the second caller. | — |
| decide-tobject-root-methods-dispatch-model | U | 65 | decide | Decide: how TObject.Equals / GetHashCode dispatch — intercept, real parent, or reserved slots |
— |
| decide-track-t-autopin-criteria | U | 55 | decide | What criteria justify Track T auto-pinning a stable binary? | — |
| decide-two-track-model-dev-and-regression-testing | T | 60 | decide | DECIDED: two operational tracks — development, and regression testing | — |
| decide-typeinfo-scalar-name-spelling | U | 20 | decide | TypeInfo(Integer)^.Name: pxx says Integer, FPC says LongInt. pxx's tyInteger and tyInt32 are separate type kinds where FPC's Integer IS LongInt, so TypeInfoOrdName picks one canonical spelling per pxx kind and Integer keeps its own. Cosmetic today (nothing branches on the string), but it is a visible FPC-parity gap in a compat-sensitive API, and changing it later breaks whatever started reading it. |
— |
| decide-typeref-gains-a-pointer-depth-field | U | 35 | decide | TTypeRef was landed to replace the 8-field tuple that ~90 sites redeclare, but as declared it carries PtrBaseTk/PtrBaseRec and DynDepth and no POINTER depth — so it cannot express ^PChar any better than the pair it replaces. Every pointer table has since grown a depth field of its own (symbols, aliases, the type parser, C params, Pascal params, captures, proc returns). Either TTypeRef gains PtrDepth and the migration folds them all in, or depth is declared to live outside TTypeRef and the migration's value shrinks. Additive either way, but it changes a shared type mid-migration. |
— |
| decide-uforth-exec-leak-strategy | U | 55 | decide | decide: how to stop the pyeval exec'd-word per-call leak (uforth doloop 553 MB) | — |
| decide-unary-minus-widening-in-the-default-dialect | U | 45 | decide | FPC widens unary minus to 64-bit for EVERY integer type; pxx truncates an UNSIGNED operand to 32 bits first, so -b shr 1 answers 2147483644 where FPC says 9223372036854775804 — in the DEFAULT dialect, not behind a flag. Adopt FPC's rule as the default, or keep ours and document the divergence? |
— |
| decide-variant-bitwise-width | U | 30 | decide | FPC narrows a Variant to 32 bits before a bitwise op, so v(-12) shr 1 is 2147483642 there; pxx works in 64 bits and its shr is arithmetic, giving -6. Three readings of one expression (FPC's, Pascal's logical shr, our sar) and they agree on every non-negative operand. Which one do we owe? |
— |
| decide-variant-tag-mismatch-policy | U | 60 | decide | Decide: what a Variant unbox does when the tag does not match the target | — |
| decide-vartype-returns-pxx-tags-not-fpc-codes | U | 30 | decide | VarType(v) returns pxx's internal tag (0..8), not FPC's varXxx code, and lib/rtl/variants.pas exports no varInteger/varDouble/varString constants at all -- so the FPC idiom if VarType(v) = varInteger does not compile. Fork: map VarType onto FPC's codes (compat, changes what existing pxx code comparing to VT_ constants sees) or export a pxx-flavoured constant set (no compat). Needs the owner's call on which surface is public. |
— |
| decide-watcher-lifecycle-manual-only | T | 50 | decide | DECIDE: the watcher daemon is started and stopped BY HAND — no supervision | — |
| decide-week-theme-2026-08-17 | U | 70 | decide | What should the next week of work aim at? Measured: the bug backlog already peaked (61 on 08-03 -> 32 now) and 65% of open tickets are features, so this is no longer a burn-down question. Three candidate themes with the numbers behind each. | — |
| decide-what-a-pin-means-and-what-may-block-one | U | 80 | decide | DECIDED 2026-09-06: both live recommendations taken. CLAUDE.md now says WHICH rows gate -- a row that restates the pin's own DEFINITION gates, every row that reports a property of the TREE grades -- and the shadow-verdict rewording is routed to Track T. The owner has stated the underlying rule at least FOUR times; that count is now in the rule, because a rule its author must repeat is being re-litigated rather than misunderstood. NOTHING IN THE TOOLING PREVENTS A PIN — verified, and this corrects the first version of this ticket. would_pin has ZERO deciding consumers (one assignment at twatch.py:2718, one comment); pin_is_green is used once, in cmd_pinstatus (trackt.py:1581), to name a ROLLBACK TARGET; and pin_shadow() says it 'deliberately never touches pinned, make pin, or stable_linux_amd64/**'. The code already implements the documented design. THE INVERSION IS IN READING: agents read 'would NOT pin' as 'cannot pin', and make pin — a ~34s human action needing only the self-host fixedpoint — was available every hour of the 49. The real cost is the RECOVERY leg, not the pin: pin_is_green needs a full run with no RED tier, nothing has qualified since v354 (2026-08-19), so the fast-pin trade's 'recovered, not prevented' has no fresh target. Owner (2026-09-01): 'a pin is a successful self compile... we added in all regression testing before we would do a full pin, but that is why we ran into this issue - no pin at all. which is a worse outcome.' The finding worth keeping is general: A SHADOW GATE THAT PUBLISHES A VERDICT NOBODY IS AUTHORISED TO ACT ON WILL BE READ AS AUTHORITY ANYWAY. |
— |
| decide-what-a-reduced-compiler-must-still-self-host | U | 55 | decide | RULED 2026-08-31 (owner): a Pascal-reduced compiler must be able to compile the FULL compiler — a bootstrap/seed property, stronger than the self-host the options debated. The first fork was VOID: it turns on a PXX_NO_PASCAL define that DOES NOT EXIST anywhere in the repo outside this ticket. The 14 real defines omit frontends (ada algol basic cfront erlang fortran lolcode nilpy rust whitespace zig) and three targets (aarch64 arm32 i386) — never Pascal and never the x86-64 host — so every buildable configuration CAN self-host and the structural-incapability case cannot arise. Second fork (what a pin gates) narrowed and still open. | — |
| decide-what-an-unwired-test-may-assert | U | 55 | decide | May we record our own output as the expectation? | — |
| decide-what-synapse-actually-needs-vs-mimic-fpc | U | 20 | decide | Synapse builds under --mimic-fpc. What does it actually NEED? |
— |
| decide-where-a-persistent-fpc-trunk-oracle-lives | U | 30 | decide | RULED 2026-09-01 (owner): option B — a persistent build at ~/src/fpc-trunk, refreshed ON REQUEST by tools/fpc_trunk.sh. Manual refresh is the ruling, not a shortcut: nightly-build testing is rare, and CLAUDE.md forbids anything timed. BUILT AND VERIFIED 2026-09-01: FPC 3.3.1 at da47439dd51b, compiling and RUNNING a program; --check reports currency. The script encodes FIVE traps, not the three in the recipe below — two more were found by running it (the compiler build leaves SEED-built RTL units so the rtl rebuild no-ops; and --check compared git HEAD, which checkout advances BEFORE the build, so it called a broken oracle CURRENT). NOTE the recipe here is STALE in one line — ~/src/fpc-source does not exist and ~/src did not either; the script clones from GitLab when the mirror is absent. ORIGINAL: The FPC trunk oracle works but has nowhere to live: a trunk build is ~4 min and ~1GB, it must sit OUTSIDE the repo, and installing into ~ needs the owner's say-so. Three options with different refresh obligations. Filed because closing feature-t-fpc-probe-needs-a-trunk-oracle with item 3 undone would otherwise lose it. |
— |
| decide-which-gtk-a-bare-gtk-gtk-h-means | U | 55 | decide | RULED 2026-08-31: GTK 3 is the default -- "i think gtk3 is a sane default in 2026". A bare <gtk/gtk.h> resolves to GTK 3; the default C include roots move from /usr/include/gtk-2.0/ to gtk-3.0/. Four hardcoded literals, not a system: cpreproc.inc:2219-2220, pasparser_proc.inc:3105, and the alias map at pasparser_proc.inc:2834-2836. Blast radius is three test files that use uses gtk. NilPy tk is NOT affected -- lib/pcl/tk.pas is a Tcl/Tk 8.6 soname embed that never touches GTK, checked not assumed. GTK 4 is unreachable regardless: its lib is installed here, its headers are not. Version selection filed separately as feature-a-gtk-version-selection-at-the-header-and-soname-layer -- the resolver half is cheap, the WIDGETSET half is a port and must not be promised with it. LANDED 2026-09-05 (frankC), five days after the ruling, as THREE literals not four: the arch-specific root was DELETED rather than moved, because GTK 3 keeps gdkconfig.h inside /usr/include/gtk-3.0/gdk/ -- which also retires the hardcoded x86_64-linux-gnu path this ticket flagged separately. TWO CORRECTIONS TO THE RULING BELOW: the blast radius is FOUR files, not three (test/test_c_gtk_window.pas is missing from the list and is the only one running a full gtk_main loop), and "those four tests never touch GTK at runtime, they compile against test/my_gtk.h" is false in both halves -- three of the four call into GTK under xvfb-run, and my_gtk.h is an ORPHAN whose only two references in the tree are a writeln string and the Makefile asserting it. Neither correction overturns the ruling; both change the estimate. |
— |
| decide-x86-64-baseline-for-arch-level-dispatch | U | 40 | decide | What x86-64 baseline does pxx target? The ticket says outright that the baseline row is the user's call, not an engineering one — and the gate box constrains it hard: plexus is Ivy Bridge (AVX, no FMA) = x86-64-v2, so a v3 baseline would SIGILL on the machine that gates every push. Whoever claims the feature otherwise has to guess something the project cannot un-choose. | — |
| decide-xml-etree-thin-tree-model-or-a-real-xml-library | U | 62 | decide | The last shim row on the corpus is xml.etree.ElementTree (4 files). MEASURED: html5lib uses it as a TREE MODEL, not as an XML library — 3 factories and 10 element members, no parse, no fromstring, no XPath, and html5lib writes its own tostring. So a ~60-line thin shim would serve every corpus caller. The fork is not effort, it is NAMING: may a module called xml.etree.ElementTree ship without the ability to parse XML? Recommendation: yes, thin, with the parser surface absent and loud. | — |
done (3927)
3927 ticket(s) — full table in BOARD-done.md, generated alongside this file.
rejected (87)
| Ticket | Track | Prio | Type | Summary | Blocked-by |
|---|---|---|---|---|---|
| bug-a-a-bare-esp-boot-issues-clock-gettime64-into-nothing | A+S | 40 | bug | REJECTED 2026-09-05: the observable is unreachable, which CLAUDE.md sends to rejected/ rather than to a low prio. A bare ESP boot does NOT issue clock_gettime64, because builtin.pas -- the unit holding the call -- DOES NOT COMPILE on --esp-profile=bare. Measured as a 2x2 rather than asserted: Randomize builds on riscv32 and xtensa under --platform=posix and is undefined variable (Randomize) on both under --esp-profile=bare. That also answers the ticket's own open xtensa question -- xtensa behaves identically, and the missing guard on the CPU_RISCV32 arm is moot because the whole unit is absent. The ticket asked for exactly this measurement before any fix and correctly refused to guess; the answer is further in the harmless direction than either option it offered. builtin.pas:459 already states the true position and should be left alone: pylib uses builtin and builtin does not compile there, if that ever changes, this is the line that fires. |
— |
| bug-a-a-class-var-declared-before-an-instance-field-corrupts-the-instance-layout | A | 80 | bug | REJECTED, FALSE PREMISE -- and the tree was contorted around it for five days, so the retraction is worth more than the ticket. A const or class var in a class body opens a SECTION, and a plain field declaration after one is ABSORBED into that section rather than ending it; var is what closes it and returns to per-instance storage. Nothing is counted into the instance layout and no field is displaced: PXXDBG=a.reclayout shows the class with NO instance fields at all, one lower on both with-fields and fields, because the field became a CLASS var. That is why two objects appear to overlap -- they read one shared global -- and TBox.r with no instance ever constructed compiles and answers, which is the discriminator that separates absorption from a wrong offset. ORACLE, measured 2026-09-19: fpc 3.2.2 on this box prints a = 0x0 from the ticket's EXACT repro, identical to pxx and equally silent, and refuses the const-section half with the same message pxx gives. The four-row table in the body is correct DATA and was read as the wrong MECHANISM; the symptom genuinely looks like aliasing, which is how it survived reduction. FIXED IN THE TREE, not in the compiler: lib/rtl/pil.pas now uses the natural order with var (PIL differential 32 rows byte-identical to Pillow 12.1.1, and it REDDENS on row 1 without the var -- positive control run), its workaround row is gone from track-b-workarounds.md, and test_a_class_var_section_absorbs_a_plain_field_until_var_closes_it.pas pins the rule as parity -- the same file under fpc 3.2.2 prints the same four rows. The pin takes the var spelling too, which lib/rtl needs. NO WARNING IS PROPOSED: a plain declaration after a class var is the legitimate spelling of a multi-entry section, so a diagnostic would fire on correct code, and that is why the language has var instead. |
— |
| bug-a-a-single-constant-is-truncated-not-narrowed-on-xtensa | A+S | 25 | bug | REJECTED, superseded within minutes by frankC's 4b6f21d68, which fixed it. The finding was right — xtensa shared riscv32's truncation, being the other soft-float ILP32 backend — but this ticket's REASON for filing instead of fixing was false: it claimed the fix was unverifiable because no float program compiles for xtensa (softfloat needs calloc). That describes the probe I tried, not the target. Reading the constant's BITS through a pointer needs no softfloat and runs on xtensa under qemu, which is how frankC measured 0 before and 1056964608 after. | — |
| bug-a-a-variant-record-with-a-shortstring-branch-is-four-bytes-larger-than-fpc | A | 45 | bug | REJECTED, FALSE PREMISE, filed and retracted the same day by its author. The oracle was fpc -O2 with no -M flag, where Integer is TWO BYTES (Turbo Pascal compatibility), so the FPC record I compared against did not contain the type mine did. Re-measured with LongInt on both sides: pxx and FPC 3.2.2 BOTH say 12, in FPC's default mode and under -Mobjfpc. The variant layout matches. tools/fpc_diff_probe.sh passes -Mobjfpc and never had this hazard; I ran fpc by hand and lost the flag the tool carries. |
— |
| bug-a-aarch64-an-aggregate-result-s-destination-is-evaluated-with-the-fp-argument-bank-unsaved | A | 20 | bug | REJECTED 2026-09-22 (frankb-8e) for aarch64 AND arm32, both measured; i386 and x86-64 have the same shape and were NOT looked at. The premise is false by CONSTRUCTION, not merely unreachable. ir_codegen_aarch64.inc's direct C-ABI arm really does save x0..x7 and not v0..v7 around the hidden-destination evaluation, and that asymmetry is CORRECT -- the operand it evaluates in that window, IRC[node], is always IRAppend(IR_LEA, scratchSym, ...) over a COMPILER-ALLOCATED scratch symbol at all five sites that build it (inlined in IRAppendCall; IRBuildHiddenDest for the CALL_IND and VIRTUAL paths), never the user's destination expression. An aggregate call returns into that scratch and the assignment to the user's lvalue is a separate copy afterwards, so a nested call inside the destination is not a shape the IR can hold, and an IR_LEA of a sym lowers to integer address materialisation that touches no v register. x0 needs saving (the LEA clobbers it); d0..d7 do not. MEASURED, not read, and by the route the original probe missed: the 2026-09-03 probe used a BODIED C function, which takes pxx's internal convention and never reaches this arm -- with an EXTERNAL of the reaching shape (extern struct D2 mk(double,double), destination v[idx()]) the arm IS reached (differential on the stp x0,x1,[sp,#-16]! signature instruction: 1 with the call, 0 without), d0/d1 are demonstrably live and unsaved across it, and the window still holds only ldr/add/mov because the nested idx() call is hoisted above the fmovs. Population: two destination shapes (nested call, float computation), aarch64, compiler sha 06255ab1878c7061. The comment at the site now states the mechanism instead of the gap. |
— |
| bug-a-an-int64-multiply-dies-with-an-illegal-instruction-on-xtensa | A | 25 | bug | REJECTED 2026-08-31, the same day I filed it: NOT A DEFECT. Both SIGILLs are one documented qemu limitation with an existing flag -- no qemu-xtensa core implements MUL32HIGH (measured across all 8 cores, before either of us arrived), so ANY 64-bit multiply dies, and integer formatting strength-reduces div-by-10 into one, which is why WriteLn(i) died too. --xtensa-soft-mulhigh makes both pass; I verified that on the PINNED compiler with only the flag varying. tools/run_target.sh:95 has carried the explanation all along. Real xtensa hardware has the instruction. The cost of filing this was not the ticket -- it is that I REMOVED A WORKING xtensa codegen arm because it produced this signal, and had to restore it. What the ticket got right is the one thing worth keeping: it refused to label the cause codegen-vs-core, and named what would settle it, so nothing false was published. Any verdict produced under the flag must SAY so -- run_target.sh notes the emulator is not bit-identical to hardware for multiplies under it. |
— |
| bug-a-elf-so-missing-pt-gnu-stack | A | 60 | bug | pxx-emitted .so has no PT_GNU_STACK, so glibc >= 2.41 refuses to dlopen it: cannot enable executable stack | — |
| bug-a-nilpy-subscript-of-a-string-literal | A | 40 | bug | NilPy: subscripting a string LITERAL is a parse error | — |
| bug-a-real-is-single-on-hosted-riscv32 | A | 35 | bug | Real is Single (4 bytes) on hosted riscv32 Linux and Double (8) on every other target and on FPC. The type is keyed on the ARCH, not on the ESP profile, so a target with no ESP in it inherits an ESP decision — silently halving the precision of every Real in a ported program. |
— |
| bug-a-test-string-n-container-strides-is-compiled-and-never-asserted | A | 50 | bug | RETRACTED by its author: the premise is FALSE. test_string_n_container_strides IS asserted -- the expect_same row keys on the BINARY name test_strn_container26, not the source path, so a grep for the source found the compile and not the compare. The one real half (dyn2dvals printing 0) was a live under-allocation and is fixed; see regression-test-core-test-string-n-container-strides. |
— |
| bug-a-threadsafe-heap-parallel-for-managed-string-race | A | 70 | bug | REJECTED — not a heap bug: was a shared captured-variable data race | — |
| bug-b-power-lost-an-ulp-on-a-half-integer-exponent | B | 40 | bug | DUPLICATE of regression-b-power-lost-a-ulp-when-it-got-26x-faster (merged 2026-08-16; its 1e300-is-stale-good finding folded in). The 26x Power/LogN rewrite (11321a09c) traded one ulp on a half-integer exponent: math.pow(2.0, 0.5) answers 1.414213562373095 where CPython and the correctly-rounded sqrt give 1.4142135623730951. It also FIXED math.pow(1e300, 1.0) (was 9.999999999999999e+299, now the exact 1e+300), so two frozen .expected rows in the nilpy suite are now stale in the good direction. | — |
| bug-b-tkhtmlview-uses-named-arguments-pascal-does-not-have | B | 60 | bug | lib/pcl/tkhtmlview.pas has never compiled: line 171 uses Python-style NAMED ARGUMENTS (configure(yscrollcommand := bar.set_)), which this dialect does not have, and calls bar.set_ where tkinter declares plain set. Any NilPy app importing tkhtmlview fails to build — songformatter does |
— |
| bug-c-invalid-symbol-in-lea-sqlite | C | 50 | bug | C: invalid symbol in lea lowering sqlite amalgamation |
— |
| bug-compiler-uses-unit-interactions | A | 50 | bug | Compiler self-build: two rough edges when uses-ing a real unit |
— |
| bug-frozen-self-build-unreliable | A | 50 | bug | Frozen-string compiler self-build (bootstrap-frozen / stabilize-frozen) is unreliable |
— |
| bug-lexer-identifier-ends-with-keyword | A | 50 | bug | Bug — Lexer misidentifies identifiers ending with keyword names (e.g. 'Class') | — |
| bug-n-0c508e507-breaks-lekkerzeilen-heapq-resolution-and-the-memoisation-did-not-restore-it | N | 90 | bug | REJECTED 2026-09-20, SAME DAY, FALSE PREMISE — 0c508e507 is exonerated and nothing here was ever a compiler defect. I bisected the BINARY LOCATION, not the compiler version. Proof, one variable: sha 7e5bea1ba120c986 run in-tree gives ok:, the byte-identical copy of that same file run from a scratchpad gives error: no member heapify came of the qualifier heapq, and the before binary run in-tree also gives ok:. --where on the scratchpad copy reports every library root MISSING. Every failing arm I reported was scratchpad-located; every passing arm was in-tree. THE CONTROL THAT FOOLED ME VARIED TWO THINGS: running another seat's compiler from my root changed the compiler AND the binary's location together, so it could not separate them, and it returned the answer I expected. The byte-identical-sha cross-check on the parent was real and made it worse, lending its credibility to the half nobody checked — a sha identifies the binary and says nothing about where it was run from. Kept loaded rather than deleted so the citation resolves and so the hazard is findable: see devdocs/dev/debugging-playbook.md, a relocated binary whose CWD fallback resolves ENOUGH to fail later with a credible domain error. |
— |
| bug-n-a-user-classs-decode-method-is-hijacked-losing-its-own-parameters | N | 70 | bug | DUPLICATE of bug-nilpy-a-callable-in-a-variable-loses-to-a-def-of-the-same-name (frank2, unfinished/), which reproduces the corpus diagnostic character for character. Kept as a record of a disconfirmed premise: filed as a decode method-name hijack, measured not to be one. The separate undefined variable (final) shape it turned up is filed as bug-n-a-keyword-argument-through-a-callable-value-is-undefined. |
— |
| bug-n-an-import-alias-cannot-shadow-a-class-or-cross-with-another-alias | N | 60 | bug | Two rows of the alias table that the proc-rebinding fix does NOT reach, because each is a different mechanism. (1) from M import f as C where M also has a CLASS named C still CONSTRUCTS M's C — the fix stamps the proc chain, and a class is not on it; FindUClass scans the real class table before the alias table, so there is no way to say C is not a class here. (2) from M import f as g, g as f answers 5 5 where CPython answers 5 18 — crossing aliases must both read the PRE-import bindings, and the second binding sees the first. Both were rows of the parent ticket; split out because neither is a variation of the proc arm. |
— |
| bug-n-the-sequence-protocol-does-not-yield-iteration | N | 48 | bug | SUPERSEDED 2026-08-19 — split and fixed under two other tickets before this one was ever dispatched. A class with __len__ + __getitem__ and no __iter__ is iterable in CPython. In NilPy for x in obj is a compile error whose diagnostic names an unrelated internal (pylib (count) not loaded), and list(obj) compiles and returns an EMPTY list — a silent wrong answer, which is the worse half. |
— |
| bug-nilpy-list-sort-rejects-key-and-reverse-with-a-bare-parse-error | N | 50 | bug | xs.sort(key=..., reverse=...) fails with a bare "unexpected token" |
— |
| bug-nilpy-uforth-rc4-corpus-stack-underflow | N | 45 | bug | WITHDRAWN — not a pxx bug. ERROR: Stack underflow came from MY harness invoking INCLUDE testje.for; uforth's INCLUDE POPS a string, so the correct form is \"testje.for\" INCLUDE. With that, all four RC4 corpora are byte-identical to CPython. |
— |
| bug-nonreproducible-miscompile-2026-06-02 | A | 50 | bug | Non-reproducible one-off miscompile (2026-06-02) | — |
| bug-p-a-deferred-generic-body-s-diagnostic-names-the-wrong-file-and-line | P | 60 | bug | Duplicate. Filed by the coordinator from frankB's rung-6b evidence within minutes of frank-rust filing the same defect from the rtl-generics probe, neither having seen the other. Merged into bug-p-a-specialized-body-reports-errors-in-the-wrong-file, which now carries both instances and is raised to p60. | — |
| bug-p-a-field-selection-on-a-record-cast-is-not-parsed | P | 35 | bug | REJECTED 2026-09-04 (frankA): false premise. A cast to a record type IS accepted as a postfix base -- eight shapes measured, all matching fpc 3.2.2, including this ticket's own line. The real content was that TMethod was not a builtin type, and the reduction was taken from the SECOND error of a two-error batch. TMethod landed at 31f8b11bf and the exact spelling now compiles at HEAD with no local declaration. Original report follows. TMethod(TSel(s.Pick)).Code — a field selected directly off a cast to a RECORD type — is expected ')' before '.'. Identical on pinned, so not a regression, and identical for every receiver spelling, so nothing to do with method references: the cast expression simply cannot be a postfix base. Assigning the cast to a variable first and selecting off that works. FPC compiles the direct form. This is the spelling several tickets USE to demonstrate other bugs (the TMethod(...).Code idiom), so it is worth fixing for the leverage as much as for itself. |
— |
| bug-p-a-generic-template-cannot-be-an-object-type | P | 0 | bug | TCustomPointersCollection<T, PT> = object is rejected with generic templates must be class, record, interface, array or procedure declarations. FPC accepts a generic over an OBJECT type; the frontend's template-kind check simply has no arm for it. This is the CURRENT stop for uses Generics.Collections (generics.collections.pas:146) — measured on both HEAD and pinned, so it is not a recent regression. |
— |
| bug-p-a-shadowed-soft-intrinsic-is-closed-without-consulting-the-arguments | P | 30 | bug | REJECTED 2026-09-09 -- THE LATENT SHAPE IS FPC'S OWN BEHAVIOUR, measured across five shadow shapes rather than reasoned about. This ticket already recorded that its live instance was gone; what it kept was the claim that a Boolean answering WHETHER (rather than WHICH) is a defect in waiting. It is not, because fpc answers WHETHER too: a user routine named Delete hides the System intrinsic ENTIRELY under fpc, in the same program, in a used unit, and even when marked overload -- three shapes, both compilers refuse, byte-for-byte the same verdict. The one shape where the two differ is the inverse of what the ticket predicts: with a SAME-ARITY wrong-type shadow (Delete(var s: AnsiString; index, count), which is exactly what the live instance had), fpc REFUSES and pxx REOPENS the intrinsic and runs the dynamic-array Delete correctly. That is us accepting what FPC rejects, which is not a defect. The fifth shape -- a shadow declared as a MEMBER of the enclosing class -- WAS a real defect, and it is not this one: pxx silently ran the member with 0.0, which turned out to be an arity hole on every bare in-class call and is fixed under bug-p-a-bare-method-call-inside-its-own-class-ignores-arity. Nothing is left here: no divergence, and the residual design observation has no reachable observable. |
— |
| bug-p-lowercase-resolves-to-a-different-implementation-in-the-seed-build | P | 45 | bug | DUPLICATE of bug-a-lowercase-resolves-to-two-different-routines-depending-on-the-seed, filed 2026-08-28. Tombstone kept so citations resolve; the surviving ticket carries this one's analysis. | — |
| bug-p-the-corpus-instance-of-the-wrong-file-diagnostic-survives-the-fix | P | 0 | bug | REJECTED — the premise is false, measured. The corpus diagnostic is CORRECT: PXXDBG=a.srcmap:* shows the error token inside SPLICE start=42607 count=27 src=generics.defaults.pas, so the tokens really did come from that file. TKey occurs zero times there because it is the SUBSTITUTED ARGUMENT, pasted in from a macro in generics.collections.pas — which is what a specialization does. Original (wrong) claim: rtl-generics still reports unknown type: TKey in generics.defaults.pas:78, a file where TKey occurs zero times, on binary a9a4818ab6c8 — AFTER the fix that closed bug-p-a-specialized-body-reports-errors-in-the-wrong-file. The reduction that ticket isolated is genuinely fixed and gated; the corpus instance is not. Two instances were merged on SIGNATURE similarity (same wrong file, same shape, two corpora) and the merge now looks wrong: one reduction's fix does nothing for the other. Do not re-merge on signature. |
— |
| bug-p-the-generics-corpus-wall-moved-backward-from-2729-to-224 | P | 55 | bug | REJECTED 2026-09-09 -- NOT A REGRESSION, the sign was inverted. The bisect is sound (ad7c03b03 is where the message changes) but the wall moved FORWARD, to the end of the unit. Two measurements: (1) on identical input -- the interface truncated at line 884, the smallest reproducing prefix -- HEAD and pin v407 BOTH fail at :224, so ad7c03b03 cannot have caused it; (2) DrainPendingPtrTargets runs at the unit's closing end. (pasparser_prog.inc:2068), so REACHING that diagnostic means the unit parsed COMPLETELY and the line it prints is where the unresolved ^T was DECLARED. 224 < 2729 compares a declaration site to a stopping point: at 178270aba the parse stopped at 2729 and the drain never ran; at ad7c03b03 it finishes and the drain finally speaks about a declaration 2500 lines earlier. This ticket's own caveat 1 was right and one step short -- 224 is not where it fails AND not where it stopped, because nothing stopped. The latent bug it exposed was real and is fixed (bug-p-a-forward-pointer-in-a-class-type-section-is-not-resolved, frankZ); verified here at binary fba33bdc4855, the driver prints drv ok. The residual -- what ad7c03b03 stopped carrying -- is retired with the sign, but the exact construct it unblocked was never identified and the corpus can no longer answer, since it passes either way. |
bug-p-a-forward-pointer-in-a-class-type-section-is-not-resolved |
| bug-pascal-local-var-not-registered-wrong-sym | P | 0 | bug | REJECTED — "a method's local is not registered" — my evidence was wrong | — |
| bug-r-fpc-seed-drift-rexprrecid-needs-a-forward | R | 60 | bug | FPC seed canary RED: rparser.inc calls RExprRecId at :1416, defined at :1754, no forward. pxx self-hosts fine (it does not require the forward); FPC does, so the cold-start bootstrap is broken. One-line fix, Track R's file. | — |
| bug-s-xtensa-cannot-link-any-program-that-uses-the-heap-runtime-calloc-is-external | S | 45 | bug | REJECTED 2026-09-05: the report is false as titled, and the part that is true is deliberate. RE-MEASURED independently on compiler 5783500470d0 and every claim holds -- xtensa links AND RUNS heap programs with NO flag: --platform=posix gives heap ok / caught boom / done under qemu, and --esp-profile=bare builds a heap program at 45540B. What refuses is the DEFAULT (IDF) profile, on purpose: compiler.pas:311 derives xtensa to PLATFORM_ESP, where externals are the IDF link's job, and the error says so at length and names both alternatives. BLOCKED-BY EDGE REMOVED: it cited the CALL0/CALL8 forward-call wall, and f49c0e11f cleared that for ordinary programs -- the working configurations above need no flag. The slug is deliberately NOT renamed despite being false; see the note in the body. |
— |
| bug-str-float-broken-by-copy-shadow | A | 50 | bug | Str() builtin breaks for float formatting when a unit shadows Copy | — |
| bug-t-a-grant-is-a-lock-the-ranker-cannot-see | T | 55 | bug | NARROWED 2026-08-30 by frankC, which found the suppression mechanism already exists and had simply not been used -- read the correction block before working this. Original framing: tools/progress.sh ready/next rank a ticket from frontmatter and print slug/prio/track. A GRANT — a coordinator handing one shared file to a named lane for the duration of a campaign — lives in the ticket BODY, so the ranker cannot see it and offers the granted file to every idle agent in that track. working/ does not cover the gap either: a lane that works in slices correctly releases the lock between them. Measured 2026-08-30: the coordinator dispatched frankA onto refactor-a-c-exclusive-lowering while frankC held a written grant on compiler/ir.inc and had four slices landed; both the ranked queue and working/ were clean, and correctly so. | — |
| bug-t-a-resolve-that-never-wrote-a-placeholder-is-uncited-and-nothing-says-so | T | 45 | bug | check counts tickets that say PENDING-COMMIT. It has nothing to say about a resolved ticket that cites no commit AT ALL — no placeholder, no sha — which is the strictly worse state, because the placeholder is the thing that announces itself. 3 of 681 tickets resolved 2026-08-16..31 are in it, all resolved by a hand-written Log line rather than progress.sh resolve. |
— |
| bug-t-check-has-no-aperture-for-a-stale-grant-or-an-absent-holder | T | 45 | bug | A grant ticket is a file lock the ranker cannot see. Filing one makes it ENUMERABLE but not CURRENT, and nothing notices when its holder's session ends. Asks for two apertures in progress.py check: GRANT-STALE (the grant's parent work is resolved) and GRANT-NO-HOLDER (owner: names no live session). Three measured instances on 2026-08-30, two of which produced a real dispatch error. | — |
| bugfix-cfront-bitfield-packing-gcc-compat | A+C | 50 | bugfix | bugfix: C front — bitfield packing GCC-compatibility | — |
| chore-inc-to-units | A | 50 | chore | .inc → real .pas units refactor |
— |
| chore-register-pxxc-domain-variants | W | 55 | chore | Register the pxxc domain variants (.com, .nl, .eu) — REJECTED | — |
| chore-runtime-emission-size | A | 50 | chore | Finer runtime-support emission (code size) | — |
| compat-pascal-binop-operand-eval-order | A | 0 | compat | pxx evaluates binary-operator operands (and CALL ARGUMENTS) left-to-right; FPC evaluates right-to-left | — |
| compat-pascal-method-impl-without-declaration | P | 0 | compat | TC.Foo implementation for a method the class never DECLARED compiles (FPC rejects) |
— |
| compat-pascal-not-of-a-cast-constant-keeps-its-width | P | 0 | compat | not Byte(0) folds to 255 in pxx and to -1 in FPC — FPC evaluates a constant not in the Int64 domain and drops the cast's width. pxx matches Delphi. Variables agree; only the constant-folded form differs. |
— |
| compat-pascal-strict-fpc-should-reject-a-duplicate-identifier-in-one-scope | A | 0 | compat | pxx compiles var p: Pointer; and procedure P(...) in the SAME scope and resolves both correctly — bare p is the variable, P(x) the routine. FPC rejects it ('overloaded identifier "p" isn't a function'), since Pascal is case-insensitive and those are one identifier. Assumed to be dialect laxness rather than a defect, on the precedent set for overload widening; --strict-fpc should reject it. Not filed as a bug: nothing resolves wrongly. |
— |
| decide-c-frontend-iso-c-or-gnu-c-by-default | U | 45 | decide | REJECTED 2026-08-27, same day, by the coordinator who filed it — the fork does not exist. Measured: the C frontend ALREADY accepts attribute, extension, _builtin*, statement expressions, asm and __inline, so GNU-by-default is the implemented status quo, not a pending decision. And accepting a SUPERSET of the standard is not a divergence from it — the same rule Pascal and NilPy already run on. Owner: 'C is well defined by formal standards... gcc is an oracle, we use it as. but it has not been an issue so far.' Original framing follows. The owner's 2026-08-27 refinement says the reference is the SPEC and an implementation's habits go behind --strict-<impl>. For C that collides with the frontend's stated purpose — real-world C compiles unmodified — because real-world C (busybox, zlib, QuickJS) is full of GNU extensions no standard describes. And --strict-gcc does not exist: C is the only frontend with no flag for its reference implementation. Recommendation: follow gcc's own precedent — default to the extended dialect, make SPEC-ONLY the opt-in (--strict-c), not the reverse. | — |
| decide-is-a-whole-python-program-meant-to-fit-inside-an-esp32 | U | 70 | decide | MEASURED FORK, not a speculative one: the NilPy runtime for the smallest program that exists (print('hi')) is ~1.74 MB on i386 and 745 KB on x86-64 even WITH dead-code elimination running, against roughly 400 KB of usable SRAM on an ESP32-C3. Over at every ceiling — 3.4x on the region we currently map, 2.2x on a C3's usable SRAM, 1.7x on an S3. The question is not how to shrink it; it is whether a whole Python program is meant to run inside the chip's own SRAM at all, or whether Python-on-ESP is a smaller thing than the hosted NilPy runtime. Flash/PSRAM and a reduced runtime profile are different PRODUCTS, not different implementations, which is what makes this the owner's and not ours. |
— |
| decide-ismultithread-runtime-flag-vs-compile-time-mode | U | 55 | decide | Delphi/FPC do not detect threading at compile time at all — they always emit the lock and skip it at runtime on a global IsMultiThread boolean. Measured here: the branch costs +5% over an unlocked refcount where an unconditional lock costs +276%. That dissolves the auto-detect question and would let TThread live in Classes unconditionally | — |
| decide-nilpy-runtime-tax-serialise-the-image-or-defer-the-bodies | U | 0 | decide | REJECTED 2026-08-31 (owner): neither A nor B. The premise is wrong. Measured the same day: the compiler parses its OWN 235,854 lines at ~12,000 lines/sec, and pylib+pyeval's 25,551 lines at ~11,600 lines/sec — the SAME RATE. There is no NilPy runtime tax; there is a general compiler throughput figure applied to 24,000 lines, reframed as a per-frontend pathology. A cache would also be invalid for the population that compiles most often (developers rebuild the compiler every fix, invalidating any compiler-keyed cache on every loop) and its failure mode is an intermittent machine-dependent wrong answer. Superseded by perf-a-the-compiler-parses-at-12k-lines-per-second-find-out-why. | — |
| decide-pin-the-bench-box-clock | U | 0 | decide | Should plexus run with turbo disabled (or a fixed governor) so bench rows are comparable by construction? It costs ~13-24% throughput on everything the box does, not just the bench, so it is not Track T's call to make silently | — |
| decide-should-the-fpc-seed-canary-be-in-the-mandatory-loop | U | 55 | decide | make compiler/pascal26 compiles with pxx, which accepts a call to a routine defined later in the same include; FPC rejects it, and FPC bootstraps this compiler. So an edit that adds a call above its definition breaks the seed while every commit stays green on the documented per-fix loop. Measured 2026-08-28: a branch was red for days across several commits, caught only by the FPC seed canary at tools/gate.sh:219, which is in the gate and not in the loop. CLAUDE.md's gating section is the owner's file, so whether the canary moves into the mandatory path is the owner's call. | — |
| decide-should-the-rust-topic-branch-be-retired-onto-master | U | 45 | decide | The 2026-08-27 per-topic tree topology puts ~/frank-rust on branch rust because topic branches carry 'destabilizing' work. frank-rust argues, with evidence, that its work has not been destabilizing: 8 commits, compiler/rparser.inc + tests + 38 Makefile lines, no shared internals, self-host byte-identical each time, gated suite. Cost of the branch: Track T sweeps origin/master only, so those 8 commits have never met the matrix, and origin/rust is already 57 behind. Decision: retire the topic branch and put Track R on master, or keep it and adopt a merge-in cadence. |
— |
| decide-t-mem-floor-policy-on-a-small-box | U | 0 | decide | MEM_FLOOR is an absolute 1500 MB, so any box with under ~1.75 GB available admits no job of any class — including a 2 GB machine, not just the 512 MB Pi. Two questions that should not be guessed: what the floor should be relative to, and whether a below-floor box should run at all or refuse loudly. The silence is fixed; the policy is not. | — |
| decide-t-refuse-unscoped-pattern-kills-in-a-hook | U | 45 | decide | MOOT 2026-08-31, not ruled on its merits. The owner is retiring fuzzing: 'i think we can stop fuzzing in general. i think we found all that csmith is able to discover by now. so that makes the hook question irrelevant.' The hook existed to stop agents pattern-killing each other's csmith batches; with no batches there is nothing to protect. Layers 1 and 3 stay landed and keep their value (docs no longer teach pkill -9 -f, tools/whokilled.sh still answers what killed a job). Worth recording that the hook was never evidence-backed either: pattern-pkill was NEVER OBSERVED -- kernel OOM and systemd-oomd were excluded, and peer SIGKILL survived as the hypothesis precisely because it leaves no trace. SCOPE SETTLED same day: csmith AND pasmith both stop -- 'we can stop fuzzing for now, our backlog big enough already. yet keep the oracle tooling, obviously.' Generators stop, ORACLES STAY (pydiff.py, gcc_diff_probe.sh, fpc_diff_probe.sh). Stopped for BACKLOG CAPACITY, not because the tools are bad -- restartable. | — |
| decide-variant-tag-space-is-a-language-wide-commitment | U | 55 | decide | WITHDRAWN — the premise was false. Escalated on a defs.inc comment claiming variant tags can never be renumbered because Pascal compares VarType() and variants are serialized. Neither binds us: variants.pas explicitly disclaims FPC compatibility, our numbers never matched FPC's varXxx anyway, and no tag reaches any durable format. Renumbering is a mechanical refactor, so this is Track A's design call, not a language decision. | — |
| decide-what-happens-to-the-136-commit-rust-branch | U | 60 | decide | origin/rust holds 136 commits of divergent work while origin/master is 222 ahead. A green Track P fix and two p65 tickets landed there tonight and were invisible to the whole fleet. CLAUDE.md says all tracks work on master and never a long-lived branch. Merge it, cherry-pick from it, or retire it -- the coordinator will not decide a 136-commit merge. | — |
| decide-when-to-move-the-pin-after-a-long-fix-run | U | 60 | decide | 32 compiler fixes sit on master unpinned; Track B builds against pinned and has a workaround waiting on the move. Pin all at once, pin incrementally, or leave it — the brake is deliberate and this is a judgment call, not a default | — |
| feature-asm-structured-ir-library | A | 50 | feature | Unify inline asm onto the existing per-target text-assembler engine | — |
| feature-dynamic-compiler-arrays-ast-fixups | A | 25 | feature | Apply the dynamic-array pattern (proven on the IR arrays) to the other fixed compiler caps: AST nodes, global fixups, label arrays | — |
| feature-lazy-standard-unit-emission | A | 50 | feature | Lazy standard-unit emission / routine-level dead-code elimination | — |
| feature-opt-a64-loadvar-destination-register | A+O | 55 | feature | EmitLoadVarA64 hardcodes x0, which is why the aarch64 leaf-operand collapse (1185b3489) could only do the CONST half. Giving it a destination-register parameter unlocks the LEAFSYM half — a further 12.6-16.3% of every integer binop on the target. Filed rather than smuggled into the port because duplicating the helper is the second-path failure this repo has a document about. | — |
| feature-opt-float-const-pool | O | 35 | feature | -O3: load float constants from a data pool, not GPR materialization | — |
| feature-opt-lazy-token-sval | O | 55 | feature | Lazy / conditional CurTok.SVal materialization — cut per-token string allocation | — |
| feature-p-a-class-body-accepts-private-type-but-not-type-private | P | 25 | feature | REJECTED 2026-09-06 on a FALSE PREMISE: fpc does NOT accept type private TF = (one, two); in a class body. It refuses it in all four modes (default, -Mobjfpc, -Mdelphi, -Mtp) with Syntax error, ":" expected but "=" found -- same line and same reason as pxx's own expected ':' before '='. The two compilers AGREE on BOTH orders: private type compiles under each and prints the same answer, type private is refused by each. The construct is fpc-testsuite tclass10b, a %FAIL row whose whole subject is that a visibility section after type resets the section so what follows must be a member and not a type -- so pxx passing that row is passing it FOR ITS OWN SUBJECT, which is the opposite of what this ticket recorded. Nothing to fix: refusing here is the specification. |
— |
| feature-t-bench-record-host-hardware-specs | T | 55 | feature | Benchmarks record the host name, but nothing about the hardware | — |
| feature-t-gcc-torture-runner | T | 20 | feature | gcc c-torture: ONE-TIME harvest of the ~50-80 runtime-fail miscompile candidates — NOT a permanent runner (dropped: mostly dialect-gap skip-list busywork) | — |
| grant-compiler-pas-c-branch-tok-unbounded-to-frankc | A+C | 50 | grant | Bounded one-line grant: frankC may set MainProgramTokCount := TOK_UNBOUNDED in the C branch of compiler.pas (~:1923), as its own commit, with tools/forwardlint.py clean before the push. Nothing else in compiler.pas. Granted because routing one line costs a full context transfer to a busy Track A agent for a line whose semantics only the C lane understands. | — |
| grant-elf-writer-and-object-writers-to-b4 | A | 50 | grant | frankA holds Track A. frank-optimize-b4 keeps a bounded file slice under A's gate: compiler/elfwriter.inc, defs.inc's ELF constants, and the object writers (writeELFRelX64 / writeELF32Rel). Dispatched by ticket, not by lane. Disjoint from symtab.inc and every frontend. | — |
| grant-lexer-writediagsourcefile-to-frankc-and-the-ir-codegen-dual-occupancy | A | 40 | grant | Two shared-file dispositions the coordinator made on 2026-08-30 and is filing rather than leaving in chat: (1) frankC gets lexer.inc bounded to WriteDiagSourceFile, for feature-c-diagnostics-name-the-module-they-are-in; (2) ir_codegen.inc is held by frankA and frankS at once, deliberately, because their edits are in disjoint functions. |
— |
| grant-pasparser-lval-and-rtti-emit-to-frankwasm-for-the-alias-break | A+P | 0 | grant | HISTORICAL RECORD — the grant system was cut on 2026-08-30 and nothing in here is an instruction any more. Kept for the correction it contains: symtab.inc makes a type well-formed, pasparser_lval.inc is what makes one EXIST, and a file list that named only the first would have landed an unnameable type. | — |
| perf-a-a-string-literal-passed-to-an-ansistring-parameter-is-copied-every-call | A | 70 | perf | REJECTED 2026-08-30 as SUPERSEDED -- do not re-land. The optimisation was real (849ms -> 84ms, measured correctly at the time) and is now worth NOTHING at the default level, because 440c822e6 promoted EmitStaticLitHandle from -O3 to -O2 THIRTY-SIX MINUTES after this landed and does the same job at codegen. Interleaved min-of-9 at HEAD: -O2 with=48ms without=41ms (no gain, marginally worse); -O1 with=50ms without=517ms (the 10x is real but only at -O1, which the owner has ruled in limbo). It also broke ~28 NilPy jobs and was reverted (72b4c47a7). The 2-line arg-tag change that fixes the NilPy break is NOT a standalone fix -- landed alone it is a FRESH regression (measured: 14 correct rows become one wrong line), because ASTTk[argVal] correctly describes what IRLowerCallArg produces on the unoptimised path. Net: land nothing. | — |
| perf-a-cache-the-compiled-nilpy-runtime-unit-image | A | 0 | perf | REJECTED 2026-09-01 (frankA), inheriting the owner's rejection of its only blocker. The premise does not survive the measurement the owner cited: the compiler parses its OWN 235,854 lines at ~12,000 lines/sec and pylib+pyeval's 25,551 at ~11,600 -- the SAME RATE, so there is no NilPy runtime tax to cache away, only general throughput applied to 24,000 lines. Superseded by perf-a-the-compiler-parses-at-12k-lines-per-second-find-out-why. ORIGINAL: The structural remainder of perf-a-every-npy-compile-still-rebuilds-the-whole-nilpy-runtime, which halved the tax again (5.36s -> 3.06s) by removing two hotspots but still does not remove the WORK: every .npy compile parses and lowers all 24,460 lines of pylib.pas + pyeval.pas before it looks at the user's program. Now that emission is fixed, the residual 2.9s is genuinely parse + AST/IR/symtab construction, so nothing short of caching the compiled unit image will move it. | decide-nilpy-runtime-tax-serialise-the-image-or-defer-the-bodies |
| refactor-a-one-predicate-for-a-tyrecord-that-is-a-fat-pointer | A | 35 | refactor | REJECTED — already done in d5fd2a6ca, forty minutes before this was filed. frankS extracted RecIsReferenceShaped (symtab.inc:8116, methodptr OR interface) and routed ProcParamIsNilable and BOTH AssignSideKind arms through it. Verified by rebuilding at HEAD and running test_methodptr_nil_assign.pas: it compiles and passes. The ticket was filed off a symtab.inc read at the sha in my checkout, without pulling first. | — |
| refactor-a-the-greenfield-frontends-share-each-others-parser-helpers | A | 18 | refactor | DUPLICATE of refactor-a-seven-frontends-borrow-rust-parser-helpers. Tombstone kept so citations resolve; the 123-places-in-zparser measurement and the substrate-doc framing were merged into the survivor. | — |
| refactor-p-the-char-array-is-not-a-string-rule-is-spelled-five-times | P | 40 | refactor | REJECTED -- false premise, and the consolidation this asks for had already happened NINE DAYS before the ticket was written. There is ONE oracle, ASTCharArrayCap in pasparser_lval.inc (landed 2026-08-20, a22177c73/3c2d75dd5), whose own header says it is the ONE oracle the char-array-is-a-string conversion asks -- both directions, every site, plus two direction wrappers. The five separate sites in ir.inc are occurrences of the TICKET SLUG in comments, not implementations of the rule: at the ticket's own filing date ir.inc held FOUR of them, and today there are four in ir.inc and four more in three other files -- eight citations of one bug, applied at the contexts where a value enters a string context (call argument, binop, assignment, write). Nothing to consolidate; counting a slug counted the bug's fame, not its spellings. |
— |
| regression-cascade-110774a14648 | T | 70 | regression | regression CASCADE: 17 jobs newly red at 110774a14648 (auto-filed by twatch) | — |
| regression-cascade-154d1aa3fba6 | T | 70 | regression | regression CASCADE: 18 jobs newly red in e417731e9..154d1aa3f (12 commits) — auto-filed by twatch | — |
| regression-cascade-2026-07-18-mass-autofile-false-positive | T | 0 | regression | regression CASCADE: 1414 stub tickets auto-filed on 2026-07-18 — all false positives | — |
| regression-cascade-3d46e52fc733 | T | 70 | regression | regression CASCADE: 1471 jobs newly red at 3d46e52fc733 (auto-filed by twatch) | — |
| regression-cascade-6906a3416548 | T | 70 | regression | regression CASCADE: 18 jobs newly red at 6906a3416548 (auto-filed by twatch) | — |
| regression-cascade-f5c8fbec-fpc-bootstrap | A | 0 | regression | Cascade sweep: 939 auto-filed regressions at f5c8fbec6016 — one root cause, already fixed | — |
| regression-optdiff-shard9-12 | A | 70 | regression | NOT A REGRESSION -- REJECTED 2026-09-16 on measurement. The diffing program is test/test_foreign_thread_exception_chain.pas, a KNOWN-FAILING repro that its own ticket (bug-a-the-exception-chain-fix-is-defeated-by-a-libc-pthread, p70, open) says is NOT WIRED because it fails. optdiff globs test/*.pas, so a program the suite deliberately excludes is swept anyway. It is NONDETERMINISTIC: 20 runs at -O0, 20 at -O2 and 16 under the PINNED v410 compiler give FOUR distinct exit codes -- 0, 124, 139 (SIGSEGV), 217 -- at a FIXED optimisation level. This ticket's OWN log tail proves it without any re-run: 217 vs 0 at -O2 and 217 vs 139 at -O3, three codes in one sweep. So the DIFF is a coin flip, the 12-commit range is a red herring, and nothing in it is causal -- the pinned control predates the whole range and flakes identically. Skipped in tools/optdiff.skip with the verification that list requires. THE UNDERLYING BUG IS REAL, OPEN AND OWNED at p70; only this regression report is wrong. |
— |
| regression-test-aarch64-test-cross-sysopen-family | T | 70 | regression | regression: test-aarch64#src:test/test_cross_sysopen_family.pas red at a5fc06ee29b6 (auto-filed by twatch) | — |
| regression-test-core-test-rust-chess-perft-full | T | 70 | regression | regression: test-core#src:test/test_rust_chess_perft_full.rs red at f5c8fbec6016 (auto-filed by twatch) | — |
| task-t-the-fifteen-reversed-rows-are-the-real-goal-1-backlog-not-the-emulator | T | 60 | task | A full tier IS green off borg as of 2026-09-22 (plexus, qemu 10.2.1, 4904 PASS / 0 FAIL / 0 FLAKY, 1283.7s, d03add15c) -- so the never-green record was never a statement about the tree, and the predecessor ticket bug-t-native-s-red-is-one-row-and-full-s-is-ninety-four-... is resolved. TWO THINGS STAND BETWEEN THAT AND GOAL 1''s "full green pin as release", and this ticket is both. (1) THAT GREEN IS NOT RELEASE-GRADE AND THE TIER SAYS SO ITSELF: 40 of its 46 skips are ABSENT CORPORA -- library_candidates/c-testsuite alone is 24 jobs, plus fpc-testsuite, fpc-rtl, lua, sqlite, cjson, fcl-json and external/synapse -- under a banner reading "A green verdict here does NOT cover them". Goal 1 wants skip_holes == 0, so the corpora have to be installed on whichever host publishes the release candidate before its verdict means what the goal needs; tools/install_lib_candidates.sh is the named route and it is itself one of the skipped jobs. (2) FIFTEEN JOB IDS ARE MATERIALLY MORE RED ON qemu 10.2.1 THAN ON 8.2.2 and they, not the emulator, are the remaining work: measured by tools/tstate_toolchain_reversals.py over native/full reports since 2026-09-04, size-canary#src:tools/size_canary.py 189/361 (52.4%) against 48/550 (8.7%), test-fpjson#src:tools/install_lib_candidates.sh 107/361 (29.6%) against 0/550, test-core#src:test/test_libwriteln_parity.pas 86/361 (23.8%) against 0/550, test-emit-obj#src:test/test_emit_obj.pas@3 65/361 (18.0%) against 0/550, three lib-test#src:test/lib_synapse*.pas rows 41/361 (11.4%) against 0/550, and eight more. THE HONEST STATUS OF THAT SECOND LIST IS THE POINT OF THE TICKET AND IT IS NOT "these rows are broken": host and toolchain are 1:1 across the whole archive window, so the reversed rates may be about the HOST (seven) rather than about the emulator, and the one direct control says they largely are -- of the eleven present in the plexus 10.2.1 full green, NINE RAN AND ALL NINE PASSED, which is ~13% likely if the rates transferred, ASSUMING INDEPENDENCE, and correlation within one run raises that, so it is a floor and evidence rather than proof. SO THE FIRST JOB HERE IS NOT FIXING FIFTEEN ROWS, IT IS DECIDING WHETHER THERE ARE FIFTEEN ROWS: re-run the reversals census after borg''s qemu moves (frankuser is carrying that escalation; it needs sudo on borg) and carry both rows rather than replacing, since a count whose ref was not recorded is unquotable rather than refuted. A row that stays red on borg AFTER the upgrade is real work in our own tree; one that clears is a fact about seven and should be struck from this list with the date. WHAT WOULD RETIRE THIS TICKET: a full tier with verdict GREEN and skip_holes == 0 on any host. WHAT WOULD RETIRE ITS NUMBERS: any re-run at a different pinned ref, and specifically the post-upgrade census above. |
— |
| wish-compile-gnu-pascal | B+C | 45 | wish | Wish: compile GPC | — |
Ready (no unmet blocker)
- [p 90] [U] decide-n-what-does-dunder-file-mean-for-a-module-inside-a-package (unblocks 1)
- [p 85] [P] bug-p-a-conditional-set-constant-whose-terms-live-two-units-away-declines (unblocks 1)
- [p 85] [P] bug-p-compile-time-info-macros-are-not-implemented-and-silently-yield-zero (unblocks 1)
- [p 85] [P] feature-b-rtl-has-no-tdoublerec (unblocks 1)
- [p 85] [P] feature-p-legacy-value-object-types (unblocks 1)
- [p 85] [T] bug-t-armed-autopin-has-refused-62-consecutive-times-and-the-tree-has-had-no-pin-for-99-hours
- [p 80] [U] decide-release-signing-key-custody (unblocks 2)
- [p 80] [T] bug-t-the-documented-build-path-never-enumerates-what-it-needs (unblocks 1)
- [p 80] [U] decide-a-is-a-pxx-object-a-self-contained-runtime-or-a-translation-unit (unblocks 1)
- [p 80] [N] bug-n-an-attribute-read-through-a-class-bound-to-a-variable-gives-a-raw-address
- [p 80] [N] bug-n-an-unpack-or-chain-store-whose-receiver-is-a-parameter-silently-does-nothing
- [p 80] [N] bug-n-annotating-a-local-that-is-returned-destroys-the-defs-inferred-return-type
- [p 80] [N] feature-n-specialise-a-dunder-body-on-the-operand-type-the-call-site-already-knows
- [p 80] [A] umbrella-track-p-and-a-have-no-open-bugs [umbrella — a GOAL, not a unit of work; take something it blocks]
- [p 75] [N] bug-nilpy-a-generator-instance-leaks-its-locals-and-argument-cells (unblocks 1)
- [p 75] [N] bug-n-a-class-level-field-annotation-is-discarded-unless-the-class-is-a-dataclass
- [p 75] [N] bug-n-a-pylib-temporary-tpylist-is-never-freed-so-format-and-set-leak-per-call
- [p 75] [N] bug-n-an-unused-import-edge-makes-a-method-receive-an-instance-of-the-wrong-class
- [p 75] [N] bug-n-lekkerzeilen-s-world-path-reads-grids-on-none-after-the-render-loop-starts
- [p 75] [P] bug-p-a-var-parameter-accepts-a-narrower-actual-and-writes-past-it
- [p 75] [N] feature-n-register-every-module-s-classes-before-any-module-s-methods-are-typed
- [p 70] [A] bug-a-a-pascal-hello-world-is-63kb-after-emission-size-dce (unblocks 2)
- [p 70] [U] decide-a-a-foreign-thread-needs-its-own-tls-block-and-the-bounds-are-the-hard-part (unblocks 2)
- [p 70] [A] feature-a-the-threadvar-area-is-3072-bytes-of-bss-in-every-program-that-has-no-threadvar (unblocks 2)
- [p 70] [A] feature-a-unreferenced-class-rtti-keeps-every-method-alive (unblocks 2)
- [p 70] [A] bug-a-a-frontend-cannot-see-that-a-backend-calls-library-routines-it-never-mentions (unblocks 1)
- [p 70] [A+N] bug-a-a-static-nilpy-program-links-the-runtime-eval-interpreter (unblocks 1)
- [p 70] [A+S] bug-a-emit-obj-retains-pxxassert-so-one-ansistring-in-it-imports-the-whole-esp-pal (unblocks 1)
- [p 70] [A] bug-a-fourteen-compiler-internal-record-names-shadow-any-user-type (unblocks 1)
- [p 70] [A] bug-a-the-heap-arena-reserves-256-mib-without-map-noreserve-so-a-small-guest-cannot-run-any-allocating-pxx-program (unblocks 1)
- [p 70] [A] bug-a-the-signal-alt-stack-is-32768-bytes-of-unconditional-bss (unblocks 1)
- [p 70] [N] bug-n-a-bitwise-or-shift-operator-on-a-variant-user-object-never-reaches-its-dunder (unblocks 1)
- [p 70] [A] feature-a-an-extern-only-variable-still-reserves-its-storage (unblocks 1)
- [p 70] [A+O] feature-opt-rtti-emit-on-use (unblocks 1)
- [p 70] [A] bug-a-the-compiler-prints-ok-with-exact-byte-counts-for-an-output-it-failed-to-write
- [p 70] [N] bug-n-a-collections-deque-segfaults-at-run-time
- [p 70] [N] bug-n-a-dynamic-attribute-store-on-a-scalar-variant-segfaults
- [p 70] [N] bug-n-a-freshly-allocated-value-whose-result-is-discarded-is-never-released
- [p 70] [N] bug-n-a-local-holding-a-callable-is-shadowed-by-a-pascal-intrinsic-at-the-call
- [p 70] [N] bug-n-a-method-receiver-parameter-must-be-literally-named-self-or-every-argument-shifts
- [p 70] [N] bug-n-a-staticmethod-called-through-cls-raises-attributeerror
- [p 70] [N] bug-n-a-write-to-a-file-that-is-never-closed-is-silently-lost
- [p 70] [N] bug-n-not-and-invert-read-the-box-of-a-name-assigned-from-arithmetic
- [p 70] [N] bug-n-the-demo-leaks-16-mb-per-two-minutes-on-a-real-world-and-it-is-not-in-the-render-path
- [p 70] [T] bug-t-a-recipe-that-self-skips-a-missing-oracle-is-not-counted-as-a-coverage-hole
- [p 70] [N] feature-n-a-call-cannot-unpack-a-sequence-into-its-arguments
- [p 70] [B] regression-lib-test-crtl-reachability-9 [track GUESSED from the test path — the defect may be in another lane; verify before claiming]
- [p 70] [B] regression-lib-test-lib-classes-tthread-2 [track GUESSED from the test path — the defect may be in another lane; verify before claiming]
- [p 70] [T] regression-optdiff-shard11-12
- [p 70] [T] regression-optdiff-shard5-12
- [p 70] [T] regression-optdiff-shard6-12
- [p 70] [A] regression-test-aarch64-test-dynarray-to-pointer-seam-leaks
- [p 70] [A] regression-test-aarch64-test-loadfile-into-element-and-field
- [p 70] [T] regression-test-c-abi-mixed-link-compiler-srchash-2
- [p 70] [T] regression-test-core-c-asm-in-inline-body-3
- [p 70] [T] regression-test-core-c-cross-time-and-exit-through-the-pal
- [p 70] [T] regression-test-core-test-dynarray-to-pointer-seam-leaks-2
- [p 70] [T] regression-test-core-test-interface-containers-2
- [p 70] [N] regression-test-core-test-nilpy-qualifier-vs-cproc-2 [track GUESSED from the test path — the defect may be in another lane; verify before claiming]
- [p 70] [N] regression-test-core-test-nilpy-unbound-builtin-method-2 [track GUESSED from the test path — the defect may be in another lane; verify before claiming]
- [p 70] [P] regression-test-core-test-opt-store-reload-2 [track GUESSED from the test path — the defect may be in another lane; verify before claiming]
- [p 70] [T] regression-test-core-test-promoint-array-cleanup-2
- [p 70] [T] regression-test-core-test-set-in-64bit-element
- [p 70] [A] regression-test-debug-g-compiler-srchash-2
- [p 70] [T] regression-test-emit-obj-c-obj-data-import-2
- [p 70] [N] regression-test-nilpy-test-cpyext-args-errors-2 [track GUESSED from the test path — the defect may be in another lane; verify before claiming]
- [p 70] [N] regression-test-nilpy-test-cpyext-containers-2 [track GUESSED from the test path — the defect may be in another lane; verify before claiming]
- [p 70] [N] regression-test-nilpy-test-cpyext-cython-2 [track GUESSED from the test path — the defect may be in another lane; verify before claiming]
- [p 70] [N] regression-test-nilpy-test-cpyext-errformat-2 [track GUESSED from the test path — the defect may be in another lane; verify before claiming]
- [p 70] [N] regression-test-nilpy-test-cpyext-hello-2 [track GUESSED from the test path — the defect may be in another lane; verify before claiming]
- [p 70] [N] regression-test-nilpy-test-cpyext-markupsafe-2 [track GUESSED from the test path — the defect may be in another lane; verify before claiming]
- [p 70] [N] regression-test-nilpy-test-nilpy-dotted-package-import-3 [track GUESSED from the test path — the defect may be in another lane; verify before claiming]
- [p 70] [T] regression-test-nilpy-test-nilpy-math-atan-and-atan2-bit-for-bit
- [p 70] [P] regression-test-pascal-conformance-shard0-6-5 [!! DO NOT CLAIM — the ticket says so; read it]
- [p 70] [T] regression-test-pascal-conformance-shard3-6-4
- [p 70] [T] regression-test-pascal-conformance-shard4-6-5
- [p 70] [T] regression-test-pascal-conformance-shard5-6-5
- [p 70] [T] regression-test-record-abi-mixed-link-compiler-srchash-2
- [p 70] [T] regression-test-threads-test-a-threadvar-is-per-thread-2
- [p 70] [T] regression-test-uforth-compiler-srchash
- [p 70] [T] regression-tools-devtest-00-4
- [p 70] [T] regression-tools-devtest-sh-00-2
- [p 68] [N] bug-nilpy-a-python-override-of-a-virtual-pascal-method-segfaults-when-called-back-from-the-pascal-side (unblocks 1)
- [p 68] [N] feature-nilpy-user-defined-decorators [parked — re-claim, do not duplicate]
- [p 65] [U] decide-t-the-full-suite-hook-refuses-prose-about-the-suite (unblocks 5)
- [p 65] [A] bug-a-a-hand-built-com-interface-cannot-be-called (unblocks 1)
- [p 65] [A] bug-a-rv32-has-no-timerfd-settime-and-three-skips-hid-it
- [p 65] [N] bug-n-a-def-inside-a-taken-branch-does-not-rebind-the-name
- [p 65] [N] bug-n-tuple-unpacking-of-an-inline-tuple-does-not-unpack-iterable-values
- [p 65] [N] bug-n-yield-from-is-not-implemented
- [p 65] [T] bug-t-run-target-sh-s-exit-code-is-discarded-at-1082-call-sites
- [p 65] [T] bug-t-six-real-program-jobs-are-in-no-tier-so-they-never-run
- [p 65] [T] bug-t-the-job-map-cannot-be-asked-whether-a-given-source-was-exercised
- [p 65] [N] feature-nilpy-cpyext-c-api-from-source [parked — re-claim, do not duplicate]
- [p 65] [N] feature-nilpy-thirdparty-libraries-as-targets [parked — re-claim, do not duplicate]
- [p 62] [N] bug-n-a-lambda-returning-a-user-class-instance-yields-none
- [p 62] [N] feature-n-sys-version-info-implementation-and-the-probe-suite
- [p 62] [N] feature-nilpy-enum-class [parked — re-claim, do not duplicate]
- [p 60] [A] feature-a-make-the-heap-lock-reentrant (unblocks 1)
- [p 60] [A] bug-a-a-threadvar-read-in-a-child-thread-faults-once-the-programs-globals-cross-a-size-boundary
- [p 60] [A] bug-a-the-address-of-a-string-element-is-the-literals-address
- [p 60] [A] bug-a-the-pinned-compiler-cannot-build-live-lib-rtl-and-nothing-tracks-it [!! DO NOT CLAIM — the ticket says so; read it]
- [p 60] [A+S] bug-a-xtensa-cannot-lower-a-store-through-a-pointer-so-no-c-program-that-writes-through-a-parameter-compiles
- [p 60] [B] bug-b-val-of-a-float-is-not-correctly-rounded-while-strtofloat-of-the-same-string-is
- [p 60] [E] bug-e-every-world-reports-meta-name-rijn-so-a-banner-cannot-name-the-scene
- [p 60] [N] bug-n-a-bare-tuple-returned-from-a-mimic-module-method-arrives-as-its-string-repr
- [p 60] [N] bug-n-a-from-import-alias-resolves-its-source-through-flat-scope
- [p 60] [N] bug-n-a-frozenset-returned-from-a-def-arrives-empty
- [p 60] [N] bug-n-a-lambda-returning-a-captured-heap-value-yields-none
- [p 60] [N] bug-n-a-local-named-after-its-own-def-aliases-the-function-result [parked — re-claim, do not duplicate]
- [p 60] [N] bug-n-a-qualified-def-value-read-is-invisible-when-the-def-s-module-is-parsed-first
- [p 60] [N] bug-n-a-returned-nested-def-reads-zero-for-its-captures-past-arity-three
- [p 60] [N] bug-n-a-shared-slot-class-attribute-is-invisible-to-the-dynamic-getter
- [p 60] [N] bug-n-a-sys-stream-in-a-variable-has-no-methods-and-fails-at-run-time
- [p 60] [N] bug-n-an-aliased-from-import-binds-a-same-named-pascal-routine-the-plain-spelling-refuses
- [p 60] [N] bug-n-async-def-and-await-are-not-implemented
- [p 60] [N] bug-n-len-does-not-dispatch-len-dunder-on-a-dynamically-typed-value
- [p 60] [N] bug-n-pyeval-boxes-a-freshly-built-container-into-a-variant-and-retains-it
- [p 60] [N] bug-n-the-hex-string-escape-emits-a-raw-byte-not-a-code-point
- [p 60] [N] bug-n-two-same-named-defs-in-exclusive-branches-of-one-function-collapse-silently
- [p 60] [N] bug-nilpy-songformatter-no-longer-compiles-set-callback-and-get-arity
- [p 60] [T] bug-t-the-bench-tier-published-red-twice-with-zero-bench-rows-and-no-report
- [p 60] [T] bug-t-the-full-matrix-switches-itself-off-when-the-fleet-is-busy
- [p 60] [T] bug-t-tools-devtest-is-a-growing-sequential-sweep-behind-one-budget
- [p 60] [U] decide-a-the-smallset-mechanism-is-built-and-green-does-that-change-the-park
- [p 60] [U] decide-state-the-population-beside-the-number-and-make-a-probe-s-identity-as-fine-as-its-decision
- [p 60] [U] decide-u-do-the-measurement-rules-want-a-home-that-every-seat-reads
- [p 60] [N] feature-a-declaration-phase
- [p 60] [N] feature-nilpy-process-exec-binding
- [p 60] [N] feature-nilpy-tkinter-surface-vs-a-real-application
- [p 60] [S] feature-s-the-64-kib-esp-heap-arena-is-reserved-even-when-dce-proves-the-allocator-unreachable
- [p 60] [C] idea-c-realworld-test-targets [idea — a brainstorm parent, not a unit of work; spin out a concrete ticket instead of claiming it]
- [p 60] [N] perf-n-an-imported-npy-module-costs-13x-per-function-versus-the-same-code-inline
- [p 60] [A] refactor-a-c-exclusive-lowering-has-no-carved-out-file-so-track-c-cannot-be-staffed [!! DO NOT CLAIM — the ticket says so; read it]
- [p 60] [T] task-t-a-release-grade-full-green-needs-the-corpora-installed-skip-holes-is-forty
- [p 60] [U] task-u-evaluate-the-2026-08-31-ticket-rules-next-week
- [p 58] [N] bug-n-a-module-level-instance-called-by-name-in-a-function-constructs-instead-of-calling
- [p 58] [N] feature-nilpy-small-syntax-gaps-found-by-the-2026-08-06-sweep
- [p 55] [M] feature-port-windows-pe (unblocks 3)
- [p 55] [U] decide-should-a-python-program-that-imports-threading-compile-as-written (unblocks 1)
- [p 55] [U] decide-the-utf16-payload-fact-is-spelled-twice-kind-widestr-and-enc-ucs2 (unblocks 1)
- [p 55] [T] feature-t-freebsd-image-and-runner (unblocks 1)
- [p 55] [A] bug-a-a-wasm32-program-whose-entry-lowers-to-unreachable-builds-green-and-traps-on-the-first-instruction
- [p 55] [A] bug-a-address-of-an-open-array-element-points-at-the-marshalling-temp
- [p 55] [A] bug-a-an-indexed-shortstring-sysopen-path-segfaults-on-x86-64
- [p 55] [A] bug-a-compiler-emitted-runtime-stubs-are-invisible-to-every-gate-we-run
- [p 55] [A] bug-a-test-object-value-type-is-red-on-borg-and-green-on-plexus
- [p 55] [A] bug-a-typeinfo-does-not-return-one-shape-of-pointer
- [p 55] [A] bug-a-wait4-does-not-write-rusage-on-riscv32
- [p 55] [N] bug-n-a-classmethod-cannot-call-another-through-cls
- [p 55] [N] bug-n-a-def-in-an-imported-module-does-not-shadow-len-or-sorted
- [p 55] [N] bug-n-a-keyword-argument-through-a-callable-value-is-refused-above-four-positionals
- [p 55] [N] bug-n-a-keyword-argument-through-a-class-value-is-refused-at-runtime
- [p 55] [N] bug-n-a-keyword-argument-through-a-procedural-field-needs-a-plain-receiver
- [p 55] [N] bug-n-a-qualified-member-call-still-consults-the-global-c-overload-set
- [p 55] [N] bug-n-a-subpackage-directory-does-not-resolve-as-a-module
- [p 55] [N] bug-n-a-tuple-returning-str-method-prints-raw-memory-when-returned-from-a-def
- [p 55] [N] bug-n-a-tuple-unpacking-assignment-does-not-box-a-callable-value
- [p 55] [N] bug-n-a-uforth-corpus-timeout-is-reported-as-a-cpython-divergence
- [p 55] [N] bug-n-a-variant-default-parameter-arrives-as-none-from-nilpy-while-typed-defaults-apply
- [p 55] [N] bug-n-an-overloaded-constructor-is-picked-by-name-ignoring-argument-type
- [p 55] [N] bug-n-compiling-html5lib-trie-never-terminates
- [p 55] [N] bug-n-hasattr-with-a-computed-name-cannot-see-a-builtin-method
- [p 55] [N] bug-n-inline-cast-deref-loses-a-pointer-fields-pointee
- [p 55] [N] bug-n-keys-through-an-untyped-receiver-is-not-dispatched-cross-module
- [p 55] [N] bug-n-min-and-max-as-a-value-bind-to-the-two-argument-arm-in-the-wrong-unit
- [p 55] [N] bug-n-reading-the-typeerror-a-unary-minus-raised-inside-a-def-segfaults
- [p 55] [N] bug-n-super-as-an-expression-fails-with-a-misleading-diagnostic
- [p 55] [N] bug-n-unary-dunders-do-not-dispatch-on-a-variant-operand
- [p 55] [N] bug-nilpy-calling-a-duplicated-ordinary-method-segfaults
- [p 55] [T] bug-t-a-backgrounded-tier-reports-the-wrappers-exit-code-over-the-tiers-verdict
- [p 55] [T] bug-t-a-probe-that-exits-2-to-say-its-instrument-is-broken-is-published-as-a-compiler-red
- [p 55] [T] bug-t-a-tier-job-identifier-is-a-selector-doing-double-duty-as-a-label
- [p 55] [T] bug-t-no-automated-check-builds-lekkerzeilen-so-a-stated-goal-demo-can-break-silently
- [p 55] [T] bug-t-the-five-gtk-regressions-are-one-missing-host-dependency
- [p 55] [T] bug-t-the-pascal-i386-relocation-row-asserts-a-count-with-no-precondition-and-passes-on-nothing
- [p 55] [T] bug-t-would_pin-false-reads-as-a-refusal-and-must-say-what-it-is
- [p 55] [U] decide-a-latent-defect-ticket-should-block-the-work-that-makes-it-observable
- [p 55] [U] decide-c-should-a-libc-symbol-from-an-unresolvable-header-bind-to-libc
- [p 55] [U] decide-do-we-introduce-the-named-trade-off-flag-axis-and-what-is-the-bar
- [p 55] [U] decide-nilpy-ranking-is-shaped-by-a-low-dependency-sample
- [p 55] [U] decide-the-proof-grade-gate-is-unsatisfiable-on-the-host-that-does-the-sweeping
- [p 55] [U] decide-the-reflog-attribution-rule-in-claude-md-misses-the-majority-of-commits
- [p 55] [U] decide-who-reads-progress-sh-check
- [p 55] [U] decide-widening-to-the-group-sends-every-agent-to-the-same-folder
- [p 55] [A] feature-a-a-target-generic-resolve-and-compare-harness-for-emit-obj-objects
- [p 55] [N] feature-n-a-kwargs-collecting-callee-through-a-callable-value
- [p 55] [N] feature-n-register-the-class-shells-of-the-import-closure-before-parsing-any-body
- [p 55] [N] feature-nilpy-lambda-compiled-closure
- [p 55] [N] feature-nilpy-no-type-inference-switch
- [p 55] [N] feature-nilpy-str-format-named-keyword-fields
- [p 55] [T] feature-t-twatch-should-assert-its-repro-selector-resolves-to-the-one-job-it-is-filing
- [p 55] [A] refactor-a-the-assignment-kind-funnel-needs-a-third-discriminator-not-a-third-special-case
- [p 55] [T] regression-cascade-154d1aa3fba6-has-no-ticket-and-its-range-cannot-explain-its-jobs
- [p 50] [U] decide-t-per-assertion-subjects-or-accept-the-file-level-label (unblocks 1)
- [p 50] [A] addendum-2026-09-16-value-parity-and-the-first-fully-restatable-toolchain
- [p 50] [A] addendum-the-property-warning-fires-on-the-safe-pair-and-is-silent-on-the-lethal-one
- [p 50] [A] bug-a-the-rtti-blob-is-hard-sized-to-64-bit-fields-so-half-of-it-is-padding-on-every-32-bit-target
- [p 50] [B] bug-b-mkkiosk-selfhost-compares-two-stages-so-a-pinned-seed-reports-no-fixedpoint
- [p 50] [N] bug-n-a-bare-import-of-a-c-header-only-name-builds-a-binary-that-cannot-exec
- [p 50] [N] bug-n-an-int-method-on-a-none-receiver-returns-0-instead-of-raising
- [p 50] [N] bug-n-kwargs-collector-alongside-named-params-needs-the-remainder [!! DO NOT CLAIM — the ticket says so; read it]
- [p 50] [N] bug-n-str-of-a-pascal-declared-exception-ignores-str-when-caught-as-a-base
- [p 50] [N] bug-n-struct-pack-with-a-computed-format-and-star-args-raises-typeerror
- [p 50] [T] bug-t-a-commit-made-in-the-watcher-clone-during-a-gate-is-unreachable-from-any-ref
- [p 50] [T] bug-t-a-ranked-ticket-that-blocks-itself-in-prose-is-invisible-to-every-check
- [p 50] [T] bug-t-a-stale-blocked-by-in-a-BACKLOG-folder-is-outside-every-aperture
- [p 50] [T] bug-t-pinstatus-names-a-rollback-target-nobody-validated
- [p 50] [T] bug-t-the-auto-filed-fallback-lane-routes-every-regression-to-the-one-lane-that-may-not-own-it
- [p 50] [U] decide-what-a-static-python-program-on-a-microcontroller-needs-to-write-to
- [p 50] [U] decide-what-should-a-shared-gate-do-when-its-watched-number-grows-from-normal-work
- [p 50] [U] decide-whose-job-is-it-to-notice-a-ticket-has-gone-stale
- [p 50] [D] docs-devnotes-ai-assisted-build [parked — re-claim, do not duplicate]
- [p 50] [A] feature-a-emit-eh-frame-so-an-external-profiler-can-unwind-past-the-leaf-frame
- [p 50] [B] feature-b-getfpcheapstatus-needs-always-on-heap-accounting
- [p 50] [B] feature-b-threading-condition-is-absent-and-the-corpus-that-justified-omitting-it-has-moved
- [p 50] [T] feature-t-a-test-s-expected-transcript-should-live-beside-the-pas-not-in-the-makefile-recipe
- [p 50] [C] umbrella-compile-and-run-dosbox [umbrella — a GOAL, not a unit of work; take something it blocks]
- [p 45] [U] decide-a-what-is-a-plain-frozen-strings-capacity-255-or-eight-megabytes (unblocks 2)
- [p 45] [W] feature-web-track-w-bootstrap (unblocks 2)
- [p 45] [A] feature-dynamic-compiler-tables (unblocks 1) [parked — re-claim, do not duplicate]
- [p 45] [A] bug-a-a-class-named-after-a-used-unit-cannot-be-constructed-from-outside-that-unit
- [p 45] [A] bug-a-a-cloned-thread-still-inherits-the-parents-fs-base-on-every-target-but-x86-64
- [p 45] [A] bug-a-a-nilpy-generator-slice-faults-out-of-bounds-under-wasm32
- [p 45] [A] bug-a-fourteen-compiler-internal-record-names-are-reserved-in-every-user-program
- [p 45] [A] bug-a-the-compilers-own-source-means-two-different-things-to-fpc-and-to-pxx
- [p 45] [B] bug-b-copy-cannot-compile-at-all-on-the-frozen-string-path
- [p 45] [D] bug-d-claude-md-still-prescribes-a-touch-the-stamp-fix-made-unnecessary
- [p 45] [N] bug-n-a-builtin-function-is-not-a-first-class-value
- [p 45] [N] bug-n-a-call-result-discarded-in-a-boolean-context-is-never-released
- [p 45] [N] bug-n-a-class-level-method-read-off-a-class-value-as-a-value-is-refused
- [p 45] [N] bug-n-a-free-function-keyword-argument-is-refused-in-a-pyeval-interpreted-lambda-body
- [p 45] [N] bug-n-a-keyword-argument-does-not-bind-when-a-constructor-overload-set-contains-a-zero-parameter-arm
- [p 45] [N] bug-n-a-lambda-stored-in-a-class-attribute-is-not-callable
- [p 45] [N] bug-n-a-list-and-a-set-share-one-class-so-introspection-cannot-tell-them-apart
- [p 45] [N] bug-n-a-nested-class-is-hoisted-to-module-scope-and-is-not-an-attribute-of-its-enclosing-class
- [p 45] [N] bug-n-a-procedure-shim-in-value-position-yields-a-number-not-none
- [p 45] [N] bug-n-a-scalar-expression-class-attribute-declared-after-a-method-reads-none
- [p 45] [N] bug-n-a-shim-parameter-typed-as-a-container-blocks-the-callable-value-wrapper
- [p 45] [N] bug-n-a-star-unpack-through-a-callable-value-stops-at-four-arguments
- [p 45] [N] bug-n-a-store-to-a-getter-only-property-masks-the-getter-from-then-on
- [p 45] [N] bug-n-exec-only-publishes-a-def-named-body-and-cannot-call-host-globals
- [p 45] [N] bug-n-getattr-with-a-literal-method-name-on-a-builtin-container-or-str-is-refused
- [p 45] [N] bug-n-object-is-the-one-builtin-type-name-that-is-not-a-value
- [p 45] [N] bug-n-pyfixiterableargs-is-inert-its-own-test-passes-with-it-disabled
- [p 45] [N] bug-n-the-property-conflict-warning-misses-five-of-eight-conflicts-including-the-one-that-crashed
- [p 45] [N] bug-n-type-of-a-member-read-on-a-bare-receiver-jumps-through-a-null-pointer-in-the-lekkerzeilen-demo
- [p 45] [N] bug-n-typeinfo-reads-the-wrong-token-and-switches-on-kind
- [p 45] [N] bug-n-unary-operators-and-abs-on-a-bool-keep-the-bool-tag-so-minus-true-is-true
- [p 45] [P] bug-p-pchar-of-an-ansistring-cast-of-a-literal-yields-one-garbage-byte
- [p 45] [S] bug-s-c-on-the-esp-profile-cannot-reach-crtl
- [p 45] [T] bug-t-177-slug-citations-in-compiler-and-lib-comments-resolve-to-no-ticket [!! DO NOT CLAIM — the ticket says so; read it]
- [p 45] [T] bug-t-25-of-56-make-test-targets-are-reachable-from-no-tier
- [p 45] [T] bug-t-a-gate-red-does-not-say-whether-it-is-yours-or-the-trees
- [p 45] [T] bug-t-a-negative-test-row-cannot-say-which-way-it-flipped
- [p 45] [T] bug-t-a-recipe-cannot-declare-its-own-skip-a-coverage-hole
- [p 45] [T] bug-t-a-ticket-citing-a-corpus-file-is-only-reproducible-by-whoever-has-that-corpus
- [p 45] [T] bug-t-borgs-enrolment-baseline-is-three-populations-and-only-one-is-the-tree
- [p 45] [T] bug-t-gate-sh-never-reaps-its-log-dir-and-an-exit-trap-would-break-the-tool
- [p 45] [T] bug-t-lane-attribution-has-two-instruments-that-disagree
- [p 45] [T] bug-t-ready-and-next-show-only-the-slug-so-a-corrected-title-is-invisible-at-the-point-of-choice
- [p 45] [T] bug-t-stale-park-is-the-one-prose-check-with-no-by-design-escape-so-an-adjudication-cannot-be-recorded
- [p 45] [T] bug-t-sync-sh-pulls-under-a-running-sweep-and-a-ticket-push-is-the-case-people-walk-into
- [p 45] [T] bug-t-the-aarch64-srchash-job-has-been-red-since-d36af549ea5b-with-no-ticket
- [p 45] [T] bug-t-the-conformance-runner-lets-a-caller-read-around-its-own-directive-extractor
- [p 45] [T] bug-t-the-conformance-runner-reports-an-empty-corpus-as-a-normal-green
- [p 45] [T] bug-t-the-tmp-sweep-guards-against-reaping-its-own-scratch-and-not-a-foreign-live-runs
- [p 45] [T] bug-t-thirteen-devtest-guards-assert-a-code-line-s-spelling-as-a-proxy-for-a-behaviour
- [p 45] [U] decide-a-repro-line-in-a-ticket-is-not-a-command-anyone-has-run
- [p 45] [U] decide-shift-native-width-was-never-re-confirmed-on-the-full-table
- [p 45] [U] decide-the-free-section-index-is-documented-and-unused
- [p 45] [U] decide-the-one-target-rule-should-name-the-build-host-not-only-the-architecture
- [p 45] [A] feature-a-a-table-filled-at-startup-is-sram-that-could-have-been-flash
- [p 45] [A] feature-a-a-variant-has-no-null-tag
- [p 45] [A] feature-a-crtl-is-not-large-file-safe-at-ilp32
- [p 45] [A] feature-a-getinterface-refcounting
- [p 45] [B] feature-embed-pascal-script
- [p 45] [N] feature-n-a-keyword-after-a-star-unpack-at-a-construction-is-still-refused
- [p 45] [N] feature-n-a-pxx-marker-module-so-an-application-can-ask-whether-it-is-under-pxx
- [p 45] [N] feature-n-dataclasses-replace-and-copy-copy-need-one-capability-per-object-field-enumeration
- [p 45] [N] feature-n-from-accepts-a-quoted-foreign-file
- [p 45] [N] feature-n-random-random-has-no-per-instance-rng-class-and-the-state-machinery-already-exists
- [p 45] [N] feature-nilpy-hasattr-per-instance-assigned-tracking
- [p 45] [N] feature-nilpy-methods-on-int-and-float
- [p 45] [N] feature-nilpy-multi-arg-callback-bridges
- [p 45] [N] feature-nilpy-threadsafe-containers
- [p 45] [T] feature-t-enrol-test-wasm32-in-a-tier-so-something-samples-the-backend
- [p 45] [B] perf-b-the-inverse-trig-functions-have-no-fast-arm-and-cost-16-microseconds
- [p 45] [A] refactor-a-nilpy-const-str-bypasses-both-the-literal-fast-path-and-the-call-arg-funnel
- [p 45] [A] refactor-a-seven-places-answer-which-time-syscall-on-which-target
- [p 45] [A] refactor-a-the-durable-param-row-is-hand-copied-on-three-registration-paths [parked — re-claim, do not duplicate]
- [p 45] [A] refactor-a-viscachevis-is-indexed-by-a-string-id-and-sized-by-a-unit-count
- [p 45] [N] refactor-n-the-field-type-pre-pass-asks-one-question-in-six-places
- [p 45] [N] refactor-n-two-import-handlers-are-twins
- [p 40] [A] bug-a-the-no-fpu-diagnostic-advises-uses-softfloat-which-does-not-help (unblocks 1)
- [p 40] [B] feature-b-delphi-extended-rtti-object-model (unblocks 1)
- [p 40] [A] bug-a-a-nilpy-function-named-main-is-refused-on-wasm32-only
- [p 40] [A] bug-a-a-record-parameters-type-is-not-resolved-when-its-slot-is-sized
- [p 40] [A] bug-a-a-wide-unsigned-literal-boxed-into-a-variant-stores-the-wrapped-value
- [p 40] [A] bug-a-i386-esi-and-edi-are-callee-saved-in-the-abi-and-scratch-in-this-backend
- [p 40] [A] bug-a-i386-has-no-float-writer-helper-so-three-frontends-cannot-print-a-real
- [p 40] [A] bug-a-nilpy-a-star-argument-in-a-constructor-call-does-not-parse
- [p 40] [A] bug-a-riscv32-softfloat-has-no-subnormals
- [p 40] [A] bug-a-test-x-on-the-pinned-stable-passes-on-a-foreign-architecture
- [p 40] [A] bug-a-the-clone-stub-registers-a-signal-alt-stack-on-x86-64-only
- [p 40] [A] bug-a-the-threadsafe-allocator-is-not-async-signal-safe
- [p 40] [A] bug-a-two-deref-walk-guards-send-a-resolvable-shape-to-the-fallback
- [p 40] [A] bug-a-tyunknown-is-both-untyped-pointer-and-i-read-garbage
- [p 40] [B] bug-b-the-from-scratch-crypto-has-no-side-channel-claim-and-the-vectors-cannot-find-one
- [p 40] [C] bug-c-thread-local-storage-still-shares-one-copy-off-x86-64-and-a-warning-is-all-that-stands-there
- [p 40] [N] bug-n-a-char-key-and-a-string-key-are-equal-everywhere-except-in-a-dict
- [p 40] [N] bug-n-a-class-level-method-through-a-class-value-is-refused-when-the-name-has-two-carriers
- [p 40] [N] bug-n-a-for-in-loop-that-rebinds-its-own-name-leaves-the-thread-registry-undrained
- [p 40] [N] bug-n-a-keyword-beside-a-mapping-at-an-ordinary-method-call-is-refused
- [p 40] [N] bug-n-a-plain-function-as-a-class-attribute-does-not-bind-the-receiver
- [p 40] [N] bug-n-a-unit-alias-rebind-is-silently-ignored
- [p 40] [N] bug-n-an-int-arm-of-a-conditional-expression-is-rendered-as-a-float
- [p 40] [N] bug-n-an-ordering-dunder-that-returns-a-non-bool-fails-against-a-variant-operand
- [p 40] [N] bug-n-from-package-import-submodule-binds-nothing-when-the-submodule-is-a-file
- [p 40] [N] bug-n-from-package-import-submodule-binds-the-parent-package
- [p 40] [N] bug-n-the-dunder-subscript-arm-is-duplicated-verbatim-in-two-lvalue-parsers
- [p 40] [N] bug-n-tk-got-files-are-invisible-to-testmgr-privatization
- [p 40] [N] bug-n-two-node-consumers-know-an-call-but-not-its-virtual-sibling
- [p 40] [N] bug-nilpy-a-handler-binder-unwound-past-by-a-different-exception-still-leaks
- [p 40] [N] bug-nilpy-shared-nonlocal-frame-cell-is-never-freed [parked — re-claim, do not duplicate]
- [p 40] [P] bug-p-a-bodiless-procedure-declaration-is-accepted-and-swallows-the-next-routine
- [p 40] [T] bug-t-a-restart-converts-owned-scratch-into-unowned-scratch-and-nothing-observes-it
- [p 40] [T] bug-t-check-has-no-aperture-for-a-ticket-slug-cited-in-source-and-195-of-them-resolve-to-nothing
- [p 40] [T] bug-t-pasmith-returns-only-integer-kinds-so-optfuzz-is-blind-to-the-return-type-axis
- [p 40] [T] bug-t-progress-check-conflates-corruption-with-advice-so-nothing-can-run-it
- [p 40] [T] bug-t-sync-sh-retries-a-push-whose-rebase-never-ran-and-calls-it-a-race
- [p 40] [T] bug-t-test-core-reports-only-its-first-red-so-a-tier-with-three-failures-reads-as-one
- [p 40] [T] bug-t-the-crtl-census-writes-fixed-names-into-a-caller-supplied-scratch-dir
- [p 40] [T] bug-t-the-sort-comm-locale-desync-has-now-been-found-three-times-independently
- [p 40] [T] bug-t-three-compiler-spellings-opt-out-of-the-testmgr-snapshot-silently
- [p 40] [W] bug-w-status-benchmarks-503s-while-every-sibling-page-serves
- [p 40] [U] decide-a-what-a-set-costs-bits-bytes-bounds-and-what-file-of-t-writes-to-disk
- [p 40] [U] decide-c-crtl-rand-max-is-conforming-but-breaks-real-code
- [p 40] [U] decide-crtl-s-FILE-table-is-an-unguarded-test-then-set-and-no-probe-has-caught-it
- [p 40] [U] decide-does-gate-before-you-commit-survive-when-its-only-justification-is-false
- [p 40] [U] decide-may-exports-name-a-routine-that-is-not-cdecl
- [p 40] [U] decide-nilpy-deepcopy-over-the-container-subset
- [p 40] [U] decide-should-a-failed-compiler-build-delete-its-target
- [p 40] [U] decide-should-the-full-suite-hook-match-argv-rather-than-the-whole-command-string
- [p 40] [U] decide-two-threading-docs-disagreed-for-seven-weeks
- [p 40] [A] feature-a-dce-can-drop-a-body-whose-only-stub-target-is-a-thunk-that-body-owns
- [p 40] [A] feature-a-emit-obj-record-class-abi-mode
- [p 40] [A] feature-a-report-fixed-cap-headroom
- [p 40] [B] feature-b-erroraddr-is-missing-from-system
- [p 40] [C] feature-c-crtl-resolv-h-and-the-ns-parser
- [p 40] [C] feature-c-csmith-differential-fuzzing
- [p 40] [C] feature-c-diagnostics-name-the-module-they-are-in [parked — re-claim, do not duplicate]
- [p 40] [B] feature-embed-dwscript-core
- [p 40] [N] feature-n-dataclass-frozen-true-needs-a-store-guard-not-an-acceptance
- [p 40] [N] feature-n-subprocess-run-has-no-cwd-parameter-and-adding-one-is-a-pal-signature-change
- [p 40] [N] feature-nilpy-map-over-several-iterables
- [p 40] [N] feature-nilpy-str-surface-gaps-2026-08-09
- [p 40] [A] feature-rtl-libc-frontend-sites-and-thread-errno
- [p 40] [W] feature-web-machine-readable-project-metadata
- [p 40] [N] perf-nilpy-remaining-perbyte-string-builders
- [p 40] [A] refactor-a-one-rule-spelled-two-ways-at-two-strictnesses-in-ir-lowering
- [p 40] [N] refactor-nilpy-three-places-decide-a-locals-class-identity
- [p 40] [A] regression-fpc-bootstrap-compiler-4 [track GUESSED from the test path — the defect may be in another lane; verify before claiming]
- [p 40] [A] regression-size-canary-size-canary-2
- [p 40] [A] task-a-a-fix-on-one-backend-should-name-what-it-checked-on-the-others
- [p 40] [A] task-a-add-fu-to-the-compiler-usage-line
- [p 40] [A] task-a-devdocs-developer-is-83-unowned-pages-and-73-are-two-months-stale
- [p 35] [A] bug-a-a-case-of-string-on-a-widestring-matches-nothing-under-pxx-wide-payload (unblocks 1)
- [p 35] [A] bug-a-a-char-array-in-a-string-context-stops-at-the-first-nul-and-fpc-does-not
- [p 35] [A] bug-a-a-record-mixing-an-arc-field-with-a-copy-operator-field-skips-the-operator
- [p 35] [A] bug-a-basic-string-concat-in-a-unit-free-program-is-a-compiler-error
- [p 35] [A] bug-a-hand-written-literal-short-jumps-span-emitters-that-can-grow
- [p 35] [A] bug-a-help-does-not-advertise-flags-the-compiler-accepts
- [p 35] [A] bug-a-nilpy-enumerate-over-str-inline-param-leak
- [p 35] [A] bug-a-pxx-home-is-advertised-but-not-honoured
- [p 35] [A] bug-a-the-basic-frontend-cannot-build-for-riscv32-the-only-driver-with-no-unit-pull
- [p 35] [A] bug-a-the-cross-self-host-proof-runs-a-different-configuration-than-the-native-one
- [p 35] [A] bug-a-unary-minus-on-a-variant-loses-the-sign-of-zero-because-the-correct-helper-is-not-wired
- [p 35] [A] bug-c-generic-selection-loses-an-array-elements-pointer-target-and-its-constness
- [p 35] [C] bug-c-long-double-is-8-bytes-in-pxx-and-16-in-gcc
- [p 35] [N] bug-n-a-bare-nilpy-import-falls-through-to-a-host-c-header-of-the-same-name-and-says-nothing
- [p 35] [N] bug-n-a-local-bound-to-self-loses-its-class-and-an-omitted-default-then-segfaults
- [p 35] [N] bug-n-a-relative-import-lowercases-the-module-name-but-an-absolute-one-does-not
- [p 35] [N] bug-n-a-unit-alias-bound-in-both-arms-of-a-runtime-try-answers-the-handler-s-module
- [p 35] [N] bug-n-an-override-changing-the-result-type-in-a-late-laid-out-class-is-refused
- [p 35] [N] bug-n-collections-counter-is-unreachable-through-its-qualified-spelling
- [p 35] [N] bug-n-pyeval-cannot-read-an-exponent-float-literal
- [p 35] [N] bug-n-sys-exit-is-a-halt-so-no-handler-sees-it
- [p 35] [N] bug-n-two-return-type-inference-passes-answer-different-kinds-for-one-def
- [p 35] [N] bug-nilpy-augmented-repeat-on-a-variant-target-still-rebinds
- [p 35] [N] bug-nilpy-del-on-a-plain-variable-silently-does-nothing
- [p 35] [A] bug-o-nothing-asserts-that-o2-actually-uses-the-static-literal-handle
- [p 35] [S] bug-s-a-direct-call-to-an-interrupt-routine-is-the-same-trap-return-fault-by-another-spelling
- [p 35] [T] bug-t-publishing-a-claim-requires-moving-the-tree-so-a-session-that-is-measuring-cannot-claim-anything
- [p 35] [T] bug-t-stale-park-is-dominated-by-wall-ladder-tickets-that-can-never-stop-firing
- [p 35] [A] chore-a-adopt-allocrecvar-at-the-twenty-remaining-record-temp-sites
- [p 35] [U] decide-where-the-string-delete-and-insert-routines-should-live
- [p 35] [A] feature-a-a-refusal-is-a-claim-with-a-date-on-it
- [p 35] [A] feature-a-error-does-not-halt-so-a-parse-can-be-speculative
- [p 35] [A] feature-a-one-guard-excludes-both-the-unimplementable-and-the-merely-adjacent
- [p 35] [B] feature-b-the-rtlevent-family-is-absent-from-the-threading-rtl
- [p 35] [C] feature-c-package-namespace-decision
- [p 35] [B+E] feature-demo-portable-userland
- [p 35] [N] feature-nilpy-counter-api-beyond-the-constructor
- [p 35] [A] feature-nilpy-cycle-collector
- [p 35] [N] feature-nilpy-walrus-operator
- [p 35] [A+O] feature-opt-inline-bodies-with-a-statement-level-call
- [p 35] [A+O] feature-opt-inline-procedures-the-third-admission-axis
- [p 35] [A+O] feature-opt-inline-record-splice-into-the-caller-destination
- [p 35] [T] feature-t-a-guard-whose-runtime-is-implausibly-small-for-its-population-is-sampling
- [p 35] [W] feature-web-blog-bootstrap
- [p 35] [A] idea-a-fold-the-asm-emit-harness-mock-preludes-into-one [idea — a brainstorm parent, not a unit of work; spin out a concrete ticket instead of claiming it]
- [p 35] [P] perf-p-the-pascal-parser-allocates-a-string-per-identifier-token-to-throw-it-away
- [p 35] [A] refactor-a-unify-the-five-remaining-pascal-postfix-suffix-walks
- [p 35] [T] task-t-two-standalone-checks-are-written-and-unwired-price-them-together
- [p 30] [N] bug-b-reportlab-mimic-multi-font-heap-corruption (unblocks 1) [parked — re-claim, do not duplicate]
- [p 30] [U] decide-what-should-pxx-selfcheck-assert-when-the-compiler-cannot-spawn (unblocks 1)
- [p 30] [B+S] feature-pal-esp-posix-fd-semantics (unblocks 1) [parked — re-claim, do not duplicate]
- [p 30] [A] bug-a-a-bad-value-for-a-known-option-is-reported-as-an-unknown-option
- [p 30] [A] bug-a-a-dynamic-array-value-can-be-assigned-to-a-record-variable
- [p 30] [A] bug-a-a-managed-record-function-result-runs-neither-initialize-nor-finalize
- [p 30] [A] bug-a-a-plain-type-alias-gets-its-own-rtti-blob-so-typeinfo-pointer-dispatch-misses
- [p 30] [A] bug-a-a-qword-boxes-as-vtint64-so-array-of-const-loses-unsignedness
- [p 30] [A] bug-a-a-static-array-assigned-to-a-dynamic-array-stores-its-address
- [p 30] [A] bug-a-aarch64-has-no-stack-argument-passing-for-the-three-c-abi-call-kinds
- [p 30] [A] bug-a-an-explicit-pal-dir-that-contradicts-the-target-platform-fails-deep-in-the-rtl-with-no-diagnostic
- [p 30] [A] bug-a-proc-map-emits-static-addresses-for-a-dynamic-build
- [p 30] [A] bug-a-pxxdbg-a-ir-star-silently-skips-a-program-main-body
- [p 30] [A] bug-a-shared-reports-an-internal-error-on-four-targets-where-i386-gets-a-clean-refusal
- [p 30] [A] bug-a-three-targets-refuse-a-shortstring-sysopen-path-four-implement-it
- [p 30] [A] bug-a-write-picks-a-different-float-width-per-target-and-both-disagree-with-fpc
- [p 30] [C] bug-c-sqlite-with-threadsafe-stops-at-a-stray-BEGIN_DECLS
- [p 30] [N] bug-n-a-function-value-has-no-name
- [p 30] [N] bug-n-a-local-bound-to-both-a-pascal-class-and-its-subclass-loses-subscripting
- [p 30] [N] bug-n-a-same-named-rtl-unit-shadows-both-a-relative-import-and-a-mimic-shim
- [p 30] [N] bug-n-an-ambiguous-property-store-on-a-dynamic-receiver-has-no-setter-path
- [p 30] [N] bug-n-an-imported-module-s-star-star-never-installs-pypowhook
- [p 30] [N] bug-n-os-has-no-getpid
- [p 30] [N] bug-n-property-works-as-a-decorator-but-is-not-a-builtin-name
- [p 30] [N] bug-n-pyparser-property-accessor-sites-do-not-know-an-interface-receiver
- [p 30] [P] bug-p-a-standalone-test-harness-with-string-consts-does-not-compile-under-pxx
- [p 30] [T] bug-t-a-close-publish-dies-when-the-clones-local-branch-lacks-the-stub-being-closed
- [p 30] [T] bug-t-forwardlint-has-no-notion-of-nested-scope
- [p 30] [A] chore-a-delete-the-dead-pascal-lvalue-statement-path
- [p 30] [A] chore-a-re-include-bench-timing-in-tools-devtest
- [p 30] [U] decide-is-binds-the-cpyext-runtime-the-ratified-extension-module-check
- [p 30] [U] decide-p-a-terminal-folder-that-is-unranked-is-the-wrong-home-for-a-measurement-that-explains-a-live-red
- [p 30] [U] decide-two-devdocs-directories-make-a-wrong-grep-look-like-a-refutation
- [p 30] [A] feature-a-a-byte-compare-over-padding-free-runs-would-retire-the-record-compare-unroll-cap
- [p 30] [A] feature-a-a-tagged-transient-region-for-conversions-that-have-no-handle-to-refcount
- [p 30] [A+S] feature-a-coswitch-for-xtensa-and-riscv32-the-scheduler-has-no-context-switch-there
- [p 30] [A] feature-a-finalize-for-bare-dynarray-and-variant
- [p 30] [A] feature-a-one-argv-to-frozen-filler-instead-of-x86-64s-inline-copy
- [p 30] [A] feature-a-the-fixedpoint-stamp-could-rebuild-itself-but-every-shape-costs-make-n
- [p 30] [B+E] feature-demo-nilpy-ide
- [p 30] [N] feature-nilpy-a-genexpr-is-lazy-not-materialised
- [p 30] [N] feature-nilpy-collections-and-string-methods
- [p 30] [N] feature-nilpy-cpyext-cycle-collector
- [p 30] [N] feature-nilpy-fstring-nested-spec-and-nested-fstring
- [p 30] [N] feature-nilpy-math-module-twelve-absent-names-measured
- [p 30] [A+O] feature-opt-a-wide-string-literal-should-be-a-static-block-not-a-runtime-transcode
- [p 30] [P] feature-pascal-corpus-passrc
- [p 30] [P] feature-pascal-management-operators-on-a-class-field
- [p 30] [W] feature-web-syndication-feeds
- [p 30] [P] perf-p-parsefactorcore-walks-a-92-arm-name-chain-per-factor [parked — re-claim, do not duplicate]
- [p 30] [A] refactor-a-nodearrndinfo-is-a-symtab-query-living-in-a-pascal-parser-file
- [p 30] [A] refactor-a-the-for-in-exception-runtime-trigger-is-the-whole-token-shape
- [p 30] [A] refactor-a-the-frozen-string-store-body-is-written-twice-in-three-backends
- [p 30] [A] refactor-a-two-dyn-array-depth-functions-that-drift [parked — re-claim, do not duplicate]
- [p 30] [N] refactor-n-user-class-dunders-are-dispatched-at-run-time-when-the-left-operand-is-static
- [p 30] [B] task-b-five-system-names-still-in-sysutils-are-waiting-on-a-pin-not-on-a-decision
- [p 25] [U] decide-openbsd-pinsyscalls-vs-the-rt-sigreturn-residual (unblocks 2)
- [p 25] [A] bug-wasm-hosted-compiler-crashes-node-but-not-wasmtime-on-a-full-compile (unblocks 1)
- [p 25] [U] decide-is-a-host-sdk-scanner-still-wanted-now-that-nothing-needs-one (unblocks 1)
- [p 25] [U] decide-posix-master-vs-fpc-named-master-for-the-socket-facades (unblocks 1)
- [p 25] [A] feature-t-run-the-wasi-slices-under-wasmtime-as-a-strict-second-host (unblocks 1)
- [p 25] [A+B] feature-target-wasm (unblocks 1) [parked — re-claim, do not duplicate] [!! DO NOT CLAIM — the ticket says so; read it]
- [p 25] [A] bug-a-64-bit-multiply-overflow-is-unchecked-under-q-plus-on-riscv32-and-xtensa
- [p 25] [A] bug-a-a-comment-claims-a-cow-check-for-dynamic-arrays-that-was-deleted
- [p 25] [A] bug-a-promocore-is-not-the-only-place-that-knows-the-promo-slot-layout
- [p 25] [A] bug-a-pxxcoswitch-and-pxxclone-are-missing-on-riscv32
- [p 25] [A] bug-a-the-ir-frame-op-doc-asserts-a-frame-layout-riscv32-does-not-use
- [p 25] [A] bug-a-the-token-pool-stores-text-only-for-identifiers-and-strings
- [p 25] [A] bug-a-two-copies-of-the-wasi-capability-model-one-in-the-pal-one-in-wasibackend
- [p 25] [A] bug-a-two-dozen-comments-describe-an-interface-value-as-a-16-byte-fat-pointer-and-it-is-one-pointer
- [p 25] [A+S] bug-a-xtensa-tkill-syscall-number-is-unlocated
- [p 25] [N] bug-n-a-chained-assignment-through-a-call-result-target-still-stores-right-to-left
- [p 25] [N] bug-n-a-keyword-argument-through-a-callable-field-is-refused
- [p 25] [N] bug-n-a-shim-class-reports-its-pascal-spelling-from-type-name
- [p 25] [N] bug-n-a-staticmethod-read-through-an-instance-binds-a-receiver
- [p 25] [N] bug-n-an-import-inside-exec-is-silently-skipped-and-execution-continues
- [p 25] [N] bug-n-the-lazy-builtin-constructors-and-divmod-are-still-not-values
- [p 25] [N] bug-nilpy-classmethod-constructors-on-builtin-types-are-absent
- [p 25] [O] bug-o-uforth-blocktest-runs-slower-under-pxx-than-under-cpython [parked — re-claim, do not duplicate] [!! DO NOT CLAIM — the ticket says so; read it] [umbrella — a GOAL, not a unit of work; take something it blocks]
- [p 25] [S] bug-s-install-esp32-target-names-a-package-that-is-virtual-only-on-26-04
- [p 25] [T] bug-t-the-c-conformance-corpus-is-absent-from-this-checkout-so-make-test-c-covers-less-than-its-name
- [p 25] [A] chore-progress-flag-prose-only-track-decl
- [p 25] [U] decide-t-should-a-skip-close-an-open-regression
- [p 25] [U] decide-which-way-the-wasi-capability-model-should-point-once-it-has-one-owner
- [p 25] [A] feature-a-o-the-refcount-lock-is-still-global-but-nobody-has-measured-that-it-costs
- [p 25] [A] feature-a-the-pascal-reduced-build-must-be-able-to-seed-the-full-compiler
- [p 25] [S] feature-esp-hardware-flash-validation
- [p 25] [N] feature-n-route-pypal-through-wasi-imports-so-nilpy-can-do-file-io-on-wasm32
- [p 25] [A] feature-nilpy-arc-cross-parity
- [p 25] [N] feature-nilpy-ascii-flag-fast-path
- [p 25] [N] feature-nilpy-hoist-constant-container-literals-out-of-a-loop-condition
- [p 25] [N] feature-nilpy-match-statement
- [p 25] [A+O] feature-opt-arch-level-and-dispatch
- [p 25] [C] perf-c-parse-codegen-large-file-superlinear
- [p 25] [A] refactor-a-backend-machine-code-lives-in-six-shared-files
- [p 25] [A] refactor-a-nilpy-calling-convention-logic-lives-in-the-pascal-parser-files
- [p 25] [A] refactor-a-wasm32-is-the-one-target-the-shared-scope-exit-sweep-cannot-be-ported-to-as-a-port
- [p 25] [T] task-t-a-makefile-recipe-that-is-not-valid-sh-passes-every-gate
- [p 22] [A] refactor-a-seven-frontends-borrow-rust-parser-helpers
- [p 20] [A] bug-a-a-label-inside-a-finally-body-is-a-duplicate-ir-label
- [p 20] [A] bug-a-comp-renders-as-an-integer-because-it-aliases-int64
- [p 20] [A] bug-a-irtoplevelstmt-parameter-is-a-node-index-named-k
- [p 20] [A] bug-a-set-membership-32-bit-backends-truncate-the-set-constant
- [p 20] [A] bug-a-target-enumerations-in-comments-are-stale-and-one-of-them-hid-a-live-bug
- [p 20] [B] bug-b-currheapused-does-not-return-to-its-prior-value-after-a-freed-block
- [p 20] [B+S] bug-b-terminalsize-answers-enotty-on-xtensa-and-the-probe-cannot-say-why
- [p 20] [N] bug-n-exec-ignores-a-caller-supplied-builtins-mapping
- [p 20] [N] bug-nilpy-except-tuple-binder-is-typed-by-the-first-arm-only [!! DO NOT CLAIM — the ticket says so; read it]
- [p 20] [B] chore-b-no-cross-loader-on-this-host-blocks-the-dynlib-arm-run [!! DO NOT CLAIM — the ticket says so; read it]
- [p 20] [U] decide-linking-a-so-as-if-it-were-an-object
- [p 20] [U] decide-should-writeableconst-off-be-honoured
- [p 20] [A] feature-a-merge-the-wasm-branch-the-shared-file-arms
- [p 20] [A+S] feature-a-promoint-variant-esp-targets
- [p 20] [A] feature-a-tls-stack-bounds-for-cloned-threads
- [p 20] [A] feature-a-typeinfo-integer-name-under-strict-fpc
- [p 20] [A] feature-a-why-threadsafe-needs-45pct-more-global-fixups
- [p 20] [B] feature-b-classes-has-no-tcollection-family
- [p 20] [A+S] feature-bare-esp-supports-uses-builtin
- [p 20] [A] feature-cli-widgetset-flag
- [p 20] [A] feature-cross-frontend-interop-contract
- [p 20] [N] feature-n-nilpy-ast-typing-module-scope
- [p 20] [N] feature-n-nilpy-has-no-reachable-path-to-the-sys-and-arg-intrinsics
- [p 20] [N] feature-nilpy-idf-import
- [p 20] [A+O] feature-opt-o3-register-pressure
- [p 20] [M] feature-t-windows-wine-harness
- [p 20] [A] feature-typeinfo-last-categories
- [p 20] [A] meta-constant-normalisation [meta — a standing index, never "done"; link work to it, do not claim it]
- [p 20] [B] task-b-four-fpc-build-artefacts-are-committed-under-lib-asmcore
- [p 18] [C+S] feature-c-esp-conformance-coverage
- [p 18] [A] refactor-a-search-path-helpers-live-in-the-c-preprocessor
- [p 15] [A] chore-a-retire-the-dead-pyexec-stub-and-its-stale-comments
- [p 15] [N] compat-n-repr-does-not-escape-non-printables-above-u007f
- [p 15] [P] compat-pascal-the-strict-fpc-flag-family-is-incomplete
- [p 15] [B+S] feature-dns-esp-wire-nameservers-from-lwip
- [p 15] [A] feature-n-a-quoted-from-import-reaches-another-language
- [p 15] [W] feature-promo-launch-plan
- [p 15] [A] idea-cross-namespace-ambiguity-warning [idea — a brainstorm parent, not a unit of work; spin out a concrete ticket instead of claiming it]
- [p 15] [P] task-pascal-conformance-long-tail
- [p 12] [A] bug-a-riscv32-sa-onstack-has-no-effect-under-qemu
- [p 12] [N] bug-n-abs-of-a-complex-raises-typeerror
- [p 12] [N] bug-nilpy-delattr-globals-and-locals-are-absent
- [p 12] [N] bug-nilpy-four-remaining-absent-builtins
- [p 10] [N] feature-nilpy-parallel-for-in (unblocks 1)
- [p 10] [P] compat-p-system-integer-is-smallint-in-fpc
- [p 10] [A] feature-a-shrink-managed-header-on-32-bit
- [p 10] [A+O] feature-opt-alloc-intent-hint
- [p 5] [N] feature-nilpy-nested-def-as-value
- [p 5] [A] idea-a-auto-enable-threadsafe-by-restarting-the-compile [idea — a brainstorm parent, not a unit of work; spin out a concrete ticket instead of claiming it]
- [p 5] [A] idea-adaptive-heap-growth [idea — a brainstorm parent, not a unit of work; spin out a concrete ticket instead of claiming it]
- [p 5] [A] meta-dialect-extensions-and-fpc-strict [!! DO NOT CLAIM — the ticket says so; read it] [meta — a standing index, never "done"; link work to it, do not claim it]
- [p 4] [A] bug-a-i386-arm32-and-riscv32-leak-more-than-x86-64-in-the-same-variant-string-shapes
- [p 0] [R] feature-rust-option-type [parked — re-claim, do not duplicate]
Leverage (tickets each one unblocks)
- 5 — decide-t-the-full-suite-hook-refuses-prose-about-the-suite
- 3 — feature-port-windows-pe
- 2 — bug-a-a-pascal-hello-world-is-63kb-after-emission-size-dce
- 2 — decide-a-a-foreign-thread-needs-its-own-tls-block-and-the-bounds-are-the-hard-part
- 2 — decide-a-what-is-a-plain-frozen-strings-capacity-255-or-eight-megabytes
- 2 — decide-openbsd-pinsyscalls-vs-the-rt-sigreturn-residual
- 2 — decide-release-signing-key-custody
- 2 — feature-a-the-threadvar-area-is-3072-bytes-of-bss-in-every-program-that-has-no-threadvar
- 2 — feature-a-there-is-no-read-only-load-segment-so-nothing-can-be-flash-resident
- 2 — feature-a-unreferenced-class-rtti-keeps-every-method-alive
- 2 — feature-web-track-w-bootstrap
- 2 — refactor-a-carve-the-nilpy-arms-out-of-the-shared-pascal-argument-loops
- 2 — task-e-decompose-a-lekkerzeilen-roofs-frame-so-two-perf-tickets-stop-guessing-at-their-own-prize
- 1 — bug-a-a-case-of-string-on-a-widestring-matches-nothing-under-pxx-wide-payload
- 1 — bug-a-a-frontend-cannot-see-that-a-backend-calls-library-routines-it-never-mentions
- 1 — bug-a-a-hand-built-com-interface-cannot-be-called
- 1 — bug-a-a-static-nilpy-program-links-the-runtime-eval-interpreter
- 1 — bug-a-emit-obj-retains-pxxassert-so-one-ansistring-in-it-imports-the-whole-esp-pal
- 1 — bug-a-fourteen-compiler-internal-record-names-shadow-any-user-type
- 1 — bug-a-pascal-nilpy-rust-and-zig-over-align-an-8-byte-member-on-i386
- 1 — bug-a-the-heap-arena-reserves-256-mib-without-map-noreserve-so-a-small-guest-cannot-run-any-allocating-pxx-program
- 1 — bug-a-the-no-fpu-diagnostic-advises-uses-softfloat-which-does-not-help
- 1 — bug-a-the-signal-alt-stack-is-32768-bytes-of-unconditional-bss
- 1 — bug-b-reportlab-mimic-multi-font-heap-corruption
- 1 — bug-n-a-bitwise-or-shift-operator-on-a-variant-user-object-never-reaches-its-dunder
- 1 — bug-n-os-environ-and-os-sep-are-not-values
- 1 — bug-nilpy-a-generator-instance-leaks-its-locals-and-argument-cells
- 1 — bug-nilpy-a-python-override-of-a-virtual-pascal-method-segfaults-when-called-back-from-the-pascal-side
- 1 — bug-p-a-class-nested-type-as-a-specialization-argument-resolves-at-unit-scope
- 1 — bug-p-a-conditional-set-constant-whose-terms-live-two-units-away-declines
- 1 — bug-p-compile-time-info-macros-are-not-implemented-and-silently-yield-zero
- 1 — bug-t-the-documented-build-path-never-enumerates-what-it-needs
- 1 — bug-wasm-hosted-compiler-crashes-node-but-not-wasmtime-on-a-full-compile
- 1 — decide-a-is-a-pxx-object-a-self-contained-runtime-or-a-translation-unit
- 1 — decide-how-much-string-machinery-the-basic-frontend-gets
- 1 — decide-how-the-sys-intrinsics-reach-wasi-when-the-compiler-links-no-pal
- 1 — decide-is-a-host-sdk-scanner-still-wanted-now-that-nothing-needs-one
- 1 — decide-n-what-does-dunder-file-mean-for-a-module-inside-a-package
- 1 — decide-nilpy-dict-mutation-during-iteration
- 1 — decide-nilpy-runtime-dunder-dispatch-strategy
- 1 — decide-posix-master-vs-fpc-named-master-for-the-socket-facades
- 1 — decide-should-a-python-program-that-imports-threading-compile-as-written
- 1 — decide-should-an-open-array-parameter-become-a-two-word-descriptor
- 1 — decide-t-per-assertion-subjects-or-accept-the-file-level-label
- 1 — decide-the-utf16-payload-fact-is-spelled-twice-kind-widestr-and-enc-ucs2
- 1 — decide-what-should-pxx-selfcheck-assert-when-the-compiler-cannot-spawn
- 1 — feature-a-a-stackful-coroutine-is-four-targets-only-so-examples-net-httpdemo-cannot-cross
- 1 — feature-a-an-extern-only-variable-still-reserves-its-storage
- 1 — feature-a-every-emit-obj-object-links-its-own-full-copy-of-crtl-so-n-objects-cost-n-runtimes
- 1 — feature-a-make-the-heap-lock-reentrant
- 1 — feature-a-object-output-for-arm32-and-aarch64
- 1 — feature-a-record-rtti-descriptors-for-initializearray-and-finalizearray
- 1 — feature-b-delphi-extended-rtti-object-model
- 1 — feature-b-rtl-has-no-tdoublerec
- 1 — feature-dynamic-compiler-tables
- 1 — feature-nilpy-parallel-for-in
- 1 — feature-opt-rtti-emit-on-use
- 1 — feature-os-targets-bsd-mac
- 1 — feature-p-legacy-value-object-types
- 1 — feature-pal-esp-posix-fd-semantics
- 1 — feature-pascal-management-operators-copy-and-addref
- 1 — feature-pascal-management-operators-nested-and-array
- 1 — feature-pcl-win32-widgetset
- 1 — feature-port-freebsd-native
- 1 — feature-port-openbsd-libc
- 1 — feature-release-checksums-repro
- 1 — feature-t-freebsd-image-and-runner
- 1 — feature-t-run-the-wasi-slices-under-wasmtime-as-a-strict-second-host
- 1 — feature-target-wasm
- 1 — perf-a-every-return-releases-every-managed-local-even-the-untouched-ones
- 1 — perf-n-one-computed-getattr-in-any-imported-module-boxes-every-method-in-the-program
- 1 — perf-o-the-variant-hidden-dest-clear-is-a-proc-call-where-the-store-arm-uses-an-inline-blob