Measured 2026-09-10, compiler ca814b0aabcc, tree at 5fb6e3d57 + the dead-path fix
Minimal, and it is minimal on purpose — the package, the relative spelling and
the try are all removable without changing the answer:
# dotpkg/__init__.py
def sel():
from . import two
return two, "pxx" # <- pascal26:6: error: undefined variable (two)
near: . import two return two >>> , "pxx"
lekkerzeilen/platform/init.py:95 is the same three tokens:
near: . import _pxx return _pxx >>> , "pxx"
The boundary, measured rather than guessed
| shape | result |
|---|---|
from . import two at module level, then two.B |
works |
from . import two inside a def, then two.B |
works |
from nilpy_relpkg import two, either position, then two.B |
works |
from . import two, then bare two as a value |
undefined variable |
So this is not about relative imports, not about packages, and not about position. The binding exists; it just is not a value. Every row above but the last was checked, which is what rules out the three explanations the error text invites.
Why this is two features and the second is the bigger one
The seam does both halves and neither is useful alone:
_backend, _backend_name = _select_backend() # 1. a module AS a value
gl = _backend.gl # 2. a member THROUGH a variable
open_window = _backend.open_window
-
A module as a value. A pxx module is a UNIT — a compile-time namespace, not an object — so there is no runtime thing for
return twoto push. It needs a module-object value, and the obvious cheap shape (a record or a handle naming the unit index) is only cheap until (2). -
A member read through a VARIABLE holding a module.
_backend.glcannot be resolved the waytwo.Bis, becausetwois a name the compiler resolves to a unit at parse time and_backendis a local whose value is not known until run time. That is open-world dispatch over units, and the existingqualifierdoor (no member X came of the qualifier Y) is entirely compile-time.
Do not fix (1) alone. It would make return _pxx, "pxx" compile and leave
_backend.gl to fail one line later with a different message, which is the
shape this repo keeps paying for — see the class-scope pair on 2026-09-10, where
repairing one door of two left the two disagreeing in a NEW way.
What is NOT wanted
A special case for return <module> that re-resolves the name at the call site.
It would make the demo's line 100 work and would answer wrongly the moment a
module value crosses a real boundary — stored in a list, passed as an argument,
chosen by a conditional. The corpus writes all three (_backend is a module
global read from four other modules).
Provenance
Found by clearing the wall in front of it. That ticket claimed to be "the LAST wall on platform/init.py"; it was not, and the census could not have said so — a first-failure census reports one error per subject and everything behind it is invisible. Modules-compiling delta from clearing it: 0, predicted before the re-run and matched.
The POPULATION, measured 2026-09-10 (frankB) — it is two sites, not a judgement
An ast walk over the whole lekkerzeilen corpus for a name bound to a MODULE
used in bare value position (not the base of an attribute access, not the func
of a call):
lekkerzeilen/platform/__init__.py:95 _pxx
lekkerzeilen/platform/__init__.py:97 _ctypes_backend
files: 1 sites: 2 module bindings in the corpus: 99
Two sites, one file, against 99 module bindings. That is the number to rank on, and it says the feature is narrow in this corpus even though it is broad in Python.
THE FIRST CUT OF THAT CENSUS SAID 267 AND WAS WRONG, which is worth recording
because the wrong number is the persuasive one. It counted every
from x import Vec3 name too — Vec3(...) is a Call whose func is a bare
Name, and the filter only excluded Attribute bases. Vec3 is a CLASS and
compiles fine. The filter has to ask the FILESYSTEM which bound names are
modules; nothing in the AST distinguishes from . import world (a module)
from from .world import World (a class).
Why its RANK moved without its cause changing
This is now the FIRST WALL of lekkerzeilen/platform/__init__.py, because the
wall in front of it — the dead-path import (708555fdb) — was cleared. It was
never reachable before. bindings.py sits behind it too: from . import platform then platform.KEY_ESCAPE, which is the cascade and not a second
instance.
So one fix clears two modules, and platform/ is the door to the graphics
stack. The other four platform modules wall on ctypes independently and are
NOT unlocked by this.
The measurement that would settle the DESIGN question
Every site here is a compile-time selection between two modules with the same
interface, which is what a unit alias is. But getattr(_backend, "open_audio", None) at :112 reads a member by STRING off the variable, and that cannot be a
compile-time alias. Whether a runtime module OBJECT is required, or whether
an alias plus a folded getattr covers this corpus, is one probe — and it is
the probe that decides whether this is a table entry or a value-representation
change. It has not been run.
THE DESIGN FORK, NARROWED TO A YES/NO — frankZ, 2026-09-10
Not taken. The ticket says part 2 is the bigger half and that is right; what was missing is which part 2. Measured from the call sites rather than from the shape of the feature, it splits, and only one half is large.
Every use of the module-valued variable in the seam:
gl = _backend.gl static member read
open_window = _backend.open_window static member read
return _backend.probe() static method call
getattr(_backend, "open_audio", None) RUNTIME getattr, with default
getattr(_backend, "open_controller", None) RUNTIME getattr, with default
Three of five are statically resolvable. A compile-time UNIT ALIAS — record
on the symbol which unit a variable was bound from, resolve var.member
through that unit — serves all three, is not a runtime value at all, and needs
no module object. Only the two getattr sites require a real runtime module
value with dynamic attribute lookup, and that is the large feature.
And those two are a capability probe against a stub. platform/_pxx.py
defines neither open_audio nor open_controller; _ctypes_backend.py
defines both (:220, :287). So the idiom exists to detect that the pxx
backend lacks audio and controller support, and under NilPy — where the ctypes
arm is dead — both calls always return None. _pxx.py is the stub that
task-b-write-the-lekkerzeilen-pxx-platform-backend (p85) exists to write.
So the question for whoever takes this, and it is answerable yes or no:
Does part 2 have to serve
getattron a module, or only static member reads?
If only static reads: a compile-time unit alias, tractable, and it clears the
wall. If getattr too: a runtime module object, which is a different and much
larger feature, and one whose only two call sites in this corpus are probing a
stub that another p85 ticket is going to replace. Ask task-b what _pxx will
define before building a runtime module value for two probes that may not
survive it.
CORRECTION — bindings.py is a CASCADE, not a second site
The census arithmetic lists this cause as two modules, platform/__init__ and
bindings. That is correct about modules MOVED and wrong about work: bindings
wall is no member KEY_ESCAPE came of the qualifier platform, and
KEY_ESCAPE = 27 is a plain module constant at platform/__init__.py:35.
bindings fails only because platform/__init__ does not compile. One file's
construct, two modules cleared. Sixth same-line-number-family cascade in this
corpus.
THE FORK IS NARROWER STILL — the static half ALREADY EXISTS, measured 2026-09-10 (frankZ, compiler 6d53c745ea69)
The addendum above asked whether part 2 has to serve getattr or only static
member reads, and guessed that static reads would need "a compile-time unit
alias" to be built. They do not. That alias is import ... as, it is in the
compiler today, and it serves every static row in the seam:
from . import fallback as backend # or `from pkg import fallback as backend`
backend.B # works
backend.name() # works
Both spellings measured, module level and inside a def, and — since
bug-n-a-dead-guarded-import-arm-still-binds-its-unit-alias — correctly under a
try: import ctypes / except ImportError: guard, which is the shape that
selects a backend. So of the seam's five uses of the module-valued variable,
three need no new feature at all; they need the seam written with as
instead of with a returned value.
What still does NOT work is exactly the two getattr sites:
f = getattr(backend, "open_audio", None)
# ^ pascal26: error: undefined variable (backend)
A unit alias is a compile-time namespace and has nothing to push as an argument
— the same sentence as the original ticket, now with the boundary drawn one
place further in. And those two sites are the capability probe against a stub
_pxx.py that task-b-write-the-lekkerzeilen-pxx-platform-backend (p85) is
going to replace.
So there are now THREE options, not two, and the new one is cheap
- Runtime module object + open-world dispatch. Serves everything. Large.
- Compile-time unit alias. Already built. Needs the SEAM rewritten to
from . import X as backend— legal CPython, identical behaviour there, andimport ... asis if anything the more common spelling of this idiom than returning a module from a function. CLAUDE.md permits changing lekkerzeilen where something is principally incompatible with NilPy, and a module as a first-class value is that. Cost: twogetattrprobes stop working, and under NilPy they already always answerNonebecause_pxx.pydefines neither name. - Fold
getattr(<unit alias>, "<literal>", <default>)at compile time. The receiver is a compile-time unit and the name is a literal, so the answer is decidable — the member is in the unit or it is not. That closes option 2's only gap without a runtime module value anywhere. Not measured; nobody has looked at whether thegetattrdoor can see a unit qualifier at all.
DO NOT read option 2 as free. It changes the corpus rather than the
compiler, and _backend is a module global read from four other modules — every
one of those reads has to become the alias too, or they wall on the same thing
one file further out. That is the number to get before choosing: how many of the
99 module bindings are read through a variable, not how many are used as values.
AND THE TRAP UNDER OPTION 2 WAS REAL AND IS NOW GONE. Written against
73312a7472fd — the compiler this ticket's boundary table was measured on — the
rewritten seam compiled, ran the handler, reported itself as pxx, and bound
backend to the CTYPES module, because a dead try arm still registered its unit
alias and the table is first-wins. Silent, and in the seam whose only job is to
say which backend is live. Fixed in 7ce61a896; option 2 was untakeable
before it and nothing in this ticket would have said so.
THE NUMBER THE FORK WAS WAITING ON — frankuser, 2026-09-11, compiler 35dce79343cd, tree bb8acc735
The addendum above names the measurement to get before choosing option 2: "how many of the 99 module bindings are read through a variable, not how many are used as values." Run. Five sites, ONE file, and they are exactly the five frankZ enumerated by hand — independent agreement from a written filter:
lekkerzeilen/platform/__init__.py:102 _backend.gl static
lekkerzeilen/platform/__init__.py:103 _backend.open_window static
lekkerzeilen/platform/__init__.py:151 _backend.probe static
lekkerzeilen/platform/__init__.py:112 getattr(_backend, ...) RUNTIME
lekkerzeilen/platform/__init__.py:136 getattr(_backend, ...) RUNTIME
The bare-value count came out at the same two sites frankB reported (:95 _pxx,
:97 _ctypes_backend), from a filter written independently, which is the only
reason I trust either number.
AND THE STATED REASON OPTION 2 IS "NOT FREE" IS FALSE. The ticket says "_backend
is a module global read from four other modules — every one of those reads has to
become the alias too, or they wall on the same thing one file further out."
Measured across the whole corpus: _backend is read in exactly one file.
Nothing outside platform/__init__.py names it, and nothing reads
platform._backend or imports it. The four other modules read the PUBLIC surface
— gl, open_window, probe(), backend_name() — which are module-level names
assigned from _backend at :102/:103 and returned at :142/:151. Those are
ordinary module attributes and option 2 does not touch them. So option 2 is
contained to one file and five sites, not spread across the corpus.
I cannot tell whether the "four other modules" line was a prediction or a misreading of the public surface; either way it was the load-bearing objection and it does not hold.
OPTION 3's UNMEASURED HALF, MEASURED — and it does NOT work today. The ticket
says "nobody has looked at whether the getattr door can see a unit qualifier at
all." It cannot, and it fails one step earlier than option 3 assumes:
from . import two as backend
f = getattr(backend, "B", None)
# ^ pascal26:3: error: undefined variable (backend)
The error is on the ARGUMENT, not on the lookup. A unit alias is not a resolvable
name in expression position, so there is nothing for a getattr handler to
receive — option 3 has to intercept getattr(<name>, <literal>, <default>) as a
SPECIAL FORM, before generic argument evaluation, and ask whether <name>
resolves to a unit alias. That is contained (one builtin call shape, receiver and
name both compile-time) but it is not the table entry the option describes.
AND OPTION 2 ALONE DOES NOT CLEAR THE WALL, which the three-option list does not
say. Rewriting the seam with as fixes :102/:103/:151 and leaves :112 and :136
failing on the same undefined variable. The only way to finish option 2 without
option 3 is to rewrite those two sites as direct member reads — and that is
gated on task-b (p85), because a direct backend.open_audio cannot compile
while _pxx.py is the selected backend and does not define it. So:
- option 2 + option 3 clears it now, in the compiler, no corpus dependency;
- option 2 alone clears it only after task-b lands the openers in
_pxx.py.
The yes/no the fork asked for, answered: NO runtime module object is required.
task-b's own summary says _pxx will expose "gl, open_window, probe, audio
and controller openers" — so once it lands, both backends define both probed
names and the two getattr capability probes are vestigial, detecting an
absence that no longer exists. Building open-world dispatch over units for two
probes of a stub that p85 is about to fill is the wrong trade in both directions.
Recommendation: option 3 (the getattr special form) in the compiler, plus the
seam rewritten with as. Five sites, one corpus file, and no value
representation change. Not claimed: I have not looked at where the builtin-call
path lives, so "contained" is about the SHAPE of the change and not a diff size.
INDEPENDENTLY CORROBORATED BY A STRONGER ROUTE — frankZ, 2026-09-11, corpus 9030d09
The section above reaches "getattr is the only remaining construct" by enumerating the call sites. frankZ reached it by doing the rewrite and watching where the wall goes, in a scratch copy of the corpus, which is the better instrument because it cannot miss a construct nobody thought to enumerate:
seam as-is -> undefined variable (_pxx) (platform/__init__.py:95)
seam rewritten with `as` -> _ctypes_backend.py:181 (a DEAD guarded arm; since fixed)
dead-arm bug fixed -> platform/__init__.py:108 = getattr(_backend, "open_audio", None)
Census unchanged at 23/35 before and after — the count did not move and the wall did, which is the distinction a first-wall census cannot show and is why the rewrite was worth running even though the number was flat.
So both halves of the fork are now answered from two directions that fail
differently: site enumeration (this seat) and wall-walking (frankZ). getattr
over a unit alias is the only construct left in that file, and it is decidable
at compile time — unit receiver, literal name, default present.
One caveat frankZ volunteered against their own number, and it is the same one
this seat has: "_backend is read in exactly one file" is grep over the
corpus and is true of the identifier. Neither of us has checked whether the
ticket's "read from four other modules" meant the seam's EXPORTED names instead —
which would be a different claim and an unaffected one, since those are ordinary
module attributes. Anyone acting on the containment argument should settle that
reading first; it does not change the fork's answer, only how cheap option 2 is.
And the _ctypes_backend.py:181 step above was a separate bug, now fixed: a
module named by an import standing AFTER a failed one inside the same guarded
arm was still compiled and its errors escaped, reported with the dead module's
line number and no file name against a 150-line file. CPython never imports it.
Fixed at PyParseImportUnitAs. Worth knowing because it means any earlier
measurement of this seam that saw a line number past the end of
platform/__init__.py was looking at a dead module, not at this ticket.
THE FORK IS ANSWERED BY MEASUREMENT, NOT BY ARGUMENT — frankZ, 2026-09-11
Two instruments, and the reason to trust the number is that they FAIL
DIFFERENTLY — not that one ran first. frankuser ran a written filter over the
corpus; this section rewrote the seam and watched where the wall went. Chronology
is the weakest of the available orderings and it is not the one that matters
here: an enumeration can only miss what nobody thought to enumerate, and a wall
walk can only report a wall that is actually reached, so neither can produce the
other's error. The _backend-is-one-file answer arrived independently on both
routes within the hour. Read the two sections as one finding with two
derivations.
The question this ticket's earlier addendum posed —
Does part 2 have to serve
getattron a module, or only static member reads?
— is now answered, and the method matters more than the answer. The earlier attempt at it ENUMERATED the seam's five call sites and reasoned that three were static. An enumeration can only miss what nobody thought to enumerate. This rewrote the seam and watched where the wall goes.
Three runs, one variable each, lekkerzeilen/platform/__init__.py:
| tree | wall |
|---|---|
| control | :95 undefined variable (_pxx) |
seam rewritten to from . import X as _backend |
_ctypes_backend.py:181 no member c_uint came of the qualifier ctypes |
| + [[bug-n-a-dead-guarded-import-arm-still-compiles-the-module-it-imports]] | :108 undefined variable (_backend) |
Line 108 is opener = getattr(_backend, "open_audio", None). So under the alias
spelling every static row in the seam passes — gl = _backend.gl,
open_window, _backend.probe() — and getattr over a unit alias is the only
construct left in the file. Answer: only getattr, and it is option 3.
Modules-compiling was 23 of 35 for all three runs. The count is the wrong readout here and the wall identity is the right one; banked separately in the playbook as "a flat count with a moved wall is progress the instrument reports as zero".
The middle wall was a compiler bug, not a property of the seam
_ctypes_backend.py:181 was reported against a platform/__init__.py that is
150 lines long. A module named by an import standing AFTER a failed one in the
same guarded arm was still compiled. Fixed; see the ticket linked above. Any
earlier measurement of this seam that reported a line past the end of
platform/__init__.py was reading a dead module.
What the getattr door can see today: nothing
Measured. getattr(backend, "name", None) where backend is a unit alias fails
with undefined variable (backend) raised inside ParseExpr on the RECEIVER —
the hasattr/getattr arm at pyparser.inc:46680 calls ParseExpr first, so
its own logic never runs and never sees that the receiver is a unit. That is why
nobody had looked: there is nothing at that door to look at. The intercept has
to go BEFORE the ParseExpr, and UnitDeclaresNameExactly(name, unitIdx)
(symtab.inc:1117) is the non-erroring existence test it needs.
Both corpus sites are getattr(<unit alias>, "<string literal>", <default>) —
receiver decidable, name decidable, miss has a value to answer with. No runtime
module object is required by this corpus.
CORRECTION to this ticket's stated cost for option 2
The ticket warns: "_backend is a module global read from four other modules —
every one of those reads has to become the alias too, or they wall on the same
thing one file further out." Measured at corpus 9030d09:
- the identifier
_backendappears in exactly ONE code file,platform/__init__.py; - the four other modules read the seam's exported names —
from .platform import gl,from .platform import KEY_DOWN, ..., gl,from . import platform— which are ordinary module-level attributes assigned once from_backend.
Those reads are unaffected by how _backend was bound. Option 2's cost is one
file, not five.
OPTION 3 IS BUILT — frankZ, 2026-09-11, compiler 16f9e6314ca0
getattr(<unit alias>, "<literal>", <default>) and the hasattr form are folded
at compile time in PyParseFactorCore, at the getattr/hasattr arm, above
the ParseExpr that was killing the receiver. That placement is the whole fix
and this ticket predicted it correctly: a unit is a compile-time namespace, the
bare name is not resolvable in expression position, so undefined variable (backend) was raised on the ARGUMENT and the arm's own logic never ran. Nothing
below that ParseExpr could have been taught to see a unit.
Every arm is decided before a token is consumed, which is what keeps it from becoming a second name-resolution path: what it cannot answer it does not touch, and the existing behaviour stands. It takes the fold only when
hasattr— always answerable, viaUnitDeclaresNameExactly;getattrabsent, with a default written — the default;getattrpresent as a PROC —PyMakeFuncValueFor, the same constructormath.sqrt-as-a-value already goes through, including its case-folded zero-parameter exclusion, mirrored rather than re-reasoned;getattrpresent as a SYMBOL —PyMakeIdent.
A local or global spelled like the module wins, gated on FindSym exactly as
ConsumeUnitQualifier and the value door beside it gate.
Measured against CPython, nine rows, byte-identical — the four folds above,
hasattr on all three member kinds, the folded symbol used in ARITHMETIC (a
variant that merely prints right would pass a bare print and fail this), and the
shadow control.
CORRECTION TO THIS TICKET'S OWN "EVERY STATIC ROW ALREADY PASSES" — it is a COMPILE-only claim
Both derivations above — site enumeration and wall-walking — conclude that under
the alias spelling gl = _backend.gl, open_window and _backend.probe() all
pass. Both measured COMPILATION and neither ran the result. Measured
2026-09-11 at compiler 16f9e6314ca0, a class reached through a unit alias:
| row | pxx | CPython |
|---|---|---|
gl = backend.gl then gl.VERSION |
42 | 42 |
gl = backend.gl then gl.clear() |
AttributeError at run time | cleared |
w = backend.Widget (local) then w.V |
5512600 — a raw address | 1 |
gl in lekkerzeilen IS a class, and the four modules that import it call
METHODS on it, so the arm that works is the one nobody uses. Filed as
[[bug-n-a-class-reached-through-a-unit-alias-is-not-a-value]] at p80.
Neither derivation was careless: the question both were asked is where the compile WALL goes, and a wall walk structurally cannot see past the wall it reports. But the sentence as written reads as a claim about the seam WORKING, and it is the summary line, which is the part everyone reads. A compile is not a run. The seam rewrite still clears the compile wall, which is what option 2 + option 3 claimed and all that it claimed.
The residual, named rather than left to be inferred
A member the unit declares as neither a proc nor a symbol — a CLASS — is
declined, and since 2026-09-11 it is declined BY NAME rather than by blaming the
receiver: getattr on the module backend cannot answer Widget — it names a CLASS, and a class reached through a module alias is not a value here.
The rename was frankB's finding, on review of the fold rather than a rebuild
of it, and it is a defect the fold ITSELF created. Before the fold,
undefined variable (backend) was uniformly true-ish here — the receiver did not
work for any member, so the message was blunt rather than wrong. After it, one
file can fold getattr(backend, "B", None) on one line and report backend
undefined two lines down. One construct, two doors, two answers — which is
precisely what repairing one door of two produces, and it is the class-scope pair
this ticket already cites from 2026-09-10. A note on a ticket stops the next
reader of the TICKET; the error stops the next reader of the CODE, and they are
different people.
UnitDeclaresNameExactly says yes to it, so the fold sees it and deliberately
declines. Folding it from a third route is the second path that stays broken
(devdocs/dev/normalise-dont-special-case.md); it belongs in the qualifier door.
This ticket owns that residual — it is the "then what" and it has an owner.
Related and separate, found while probing: re-exporting a unit alias across a
file boundary (from pkg import backend where pkg did from . import X as backend) fails on its own account with no member Cls came of the qualifier backend. That is a DIFFERENT construct from the seam's, which creates and reads
the alias in one file, and it is what a probe written the natural way measures
instead of the one you meant. Not folded, not this ticket.
CORRECTION — the permission to change lekkerzeilen is the OWNER'S, not CLAUDE.md's
The option list above says "CLAUDE.md permits changing lekkerzeilen where something is principally incompatible with NilPy." CLAUDE.md contains no such sentence — grepped, 2026-09-11, and the goal file does not either. The permission is real and it is stronger than the citation: it is the owner's own standing rule on this target, in his words, recorded in [[umbrella-lekkerzeilen-compiles-and-runs-under-nilpy]]:
"this time we are allow to 'cheat' on the code base. if there's something that's principally incompatible with nilpy, we could fix the source."
Worth correcting rather than leaving, because the two citations behave differently under challenge: a rule attributed to CLAUDE.md gets checked against CLAUDE.md, fails, and the whole permission then looks invented — when a seat re-derives it from the umbrella it is fine, and when a seat does not, a licensed change reads as a corpus bend. CLAUDE.md's own precedence rule makes the mis-citation worse than a missing one.
The seam, and the wall is CLEARED
Measured in a scratch copy at corpus 9030d09, compiler 16f9e6314ca0:
| seam | lekkerzeilen/platform/__init__.py |
|---|---|
as shipped (return _pxx, "pxx") |
:95 undefined variable (_pxx) |
rewritten to from . import X as _backend |
compiles |
Both rows in the same run, the as-shipped one as the control. So option 2 + option 3 clears it, exactly as this ticket's recommendation said, and no runtime module object was built.
Copy the SUBTREE, not the repo. The scratch copy cost 46G the first time and
filled /tmp for every seat on the box: lekkerzeilen's package is 2.1M and its
world/ tile dataset is 46G. cp -r /home/neo/lekkerzeilen/lekkerzeilen is the
whole corpus anyone measuring this needs.
RESOLVED 2026-09-11 — option 3 was BUILT the same day and this ticket did not say so
The fix is frankZ's, 3662f8a8b (fold getattr/hasattr over a unit alias
at compile time, intercepting ABOVE the ParseExpr on the receiver, which is
what this ticket had specified). Closed here by frankB after verifying the
ticket's own acceptance shapes rather than re-deriving the design.
Measured at compiler 465845b20d1e, byte-identical to CPython in both:
| shape | pxx | CPython |
|---|---|---|
getattr(_backend, "VALUE", None) / missing-with-default / hasattr |
42 default True |
42 default True |
the ORIGINAL report — from . import two as _backend then _backend.VALUE and _backend.fn() |
42 called |
42 called |
The second row is the one worth running: the summary had moved on to getattr
and the original report was a plain member read, so closing on the getattr
rows alone would have left the reported shape unverified.
Why it needed closing by someone who did not write the fix
The summary said, in the present tense, "this needs OPTION 3" — while option
3 was already in the tree, landed hours earlier by the seat that wrote that
sentence. Nothing was wrong with either half; a summary is written when the fork
is decided and the fix lands under its own ticket's commit message, so the
stale tense is the DEFAULT outcome rather than an oversight. It is the shape
CLAUDE.md's goal section is about: a ticket whose own body records that its
blocker is gone, sitting in a folder, ranked at effective prio 90 and handed
out by next as the top of the queue.
Residual, with an owner, because "not this" is half a finding
Two shapes reached through a unit alias are still wrong and BOTH are filed:
- a class MEMBER —
bug-n-a-class-reached-through-a-unit-alias-is-not-a-value, the method-call arm, which is import-free and is what blocks the demo; - an attribute READ off a class bound to a variable —
bug-n-an-attribute-read-through-a-class-bound-to-a-variable-gives-a-raw-address, silent, wrong value, split out on 2026-09-11.
Neither is a module-as-a-value question, which is why they are not this ticket.
No site in lekkerzeilen requires a runtime module object, which this
ticket's own last section established and which the corpus now confirms:
undefined variable (_pxx) has disappeared as a wall class entirely.
Log
- 2026-09-11 — resolved, commit 9a8c59909. The FIX is frankZ 3662f8a8b; 9a8c59909 is the close.