A one-ulp move turns the fleet red, and a red outranks its own prio
- Track: T (the tooling and what the tiers assert). The compiler is not the bug here -- do not change float behaviour under this ticket.
- Filed: 2026-08-19, alongside the
float/scope.
The mechanism
The owner's standing rule is that float accuracy is low prio by definition (see
devdocs/progress/float/README.md). float/ implements that by making the tickets invisible
to ready/next.
That governs tickets. It does not govern reds. A red job is worked at the priority of being red, not at the priority of its subject -- nobody triaging a red first asks whether its topic was de-ranked. So an accuracy regression re-enters the queue at the top through a door the prio field does not reach, which is exactly how float work kept getting scheduled while its tickets sat at p15-p30 untouched.
The concrete source: NilPy .expected files are generated by running CPython, so they
assert bit-exact float output. float/meta-float-accuracy-policy records the contradiction --
the shipped RTL policy is fast-by-default with 1-2 ulp never a bug, while the shipped tests
turn 1 ulp red in CI. Both are true of the same binary.
What to do
Make the float-accuracy assertions unable to command attention at their current strength. Shapes, in rough order of preference -- choose and state which, do not do all three:
- Tolerance, not equality. Where an expectation exists only to pin a float rendering, compare within a stated ulp tolerance. Keeps the coverage, removes the false alarm.
- Move them off the gating tiers into something run on purpose. Cheapest, loses the least.
- Regenerate the expectations from pxx rather than CPython. Cheapest of all and the worst: the tests then assert whatever we currently do, so they stop being an oracle.
Whichever is chosen, the escape rule survives it: a wrong value at scale, a saturation, a NaN/Inf handling fault or a crash must still go red at full strength. Only last-digit accuracy is being de-gated. If a shape cannot draw that line, it is the wrong shape.
Gate and scope
Track T own-tooling gate, unchanged. This touches what the tiers assert, so it lands with the coverage change stated explicitly in the commit -- a quieter fleet is only an improvement if everyone can see what went quiet.
Do not fix any float ticket while doing this; they live in float/ and are picked up on
request, not on rank.
DECIDED by the owner's ground-truth principle (2026-08-19)
"our ground truth is projects that provably work under CPython or C or Pascal. and yes, it needs dark magic at times."
This settles the shape choice, which was previously an open trade. A generated .expected
holding a float rendering is not ground truth — it is our own output frozen at a moment, or
CPython's output for a program nobody runs. Ground truth is a real project that provably
works: zlib's compressed stream matching a gcc-built zlib's, a Python package importing and
running, an FPC program producing the same answer.
So the ranking of the shapes changes on principle, not on taste:
- Shape 2 (off the gating tiers) is right, because a synthetic bit-exactness assertion was never the oracle. It gates on something that is not the truth criterion.
- Shape 1 (tolerance) is acceptable where the assertion is genuinely about a rendering someone depends on.
- Shape 3 (regenerate from pxx) stays the worst, and the principle says why sharply: it replaces an oracle with a mirror. A test that asserts what we currently do cannot tell us a real project broke.
What must NOT be de-gated, under the same principle: any assertion whose subject is a real project working — a corpus program that fails to build or produces different output, a round-trip that stops round-tripping. Those ARE ground truth even when the differing bytes are float digits. The line is what is being asserted, not what type the bytes are.
And the second half of the owner's sentence is licence, not apology: "it needs dark magic at times" — shims, mimics and facades are how a real project is made to work, so a shim existing is evidence of the strategy running correctly, never a smell.
2026-08-26 — the mechanism is fixed; the three proposed shapes are NOT the
right ones, and the evidence says why
Status: partly addressed, deliberately still open.
First, the premise checks out
The ticket asserted a mechanism. It has fired: 23 recorded red/fixed events on float-named jobs in the retained run log.
| when | tier | jobs |
|---|---|---|
| 2026-07-20 | full → native | cmath_log_correct_round_b378.c, test_nilpy_str_float.npy, test_str_float_fpc_default_b327.pas, cmath_log2_expm1_family_b382.c — red, all fixed 4h later |
| 2026-08-15/16 | full | test_nilpy_math_domain_errors.npy, test_nilpy_math_log.npy — red, fixed next day |
| 2026-08-15 | native | test_nilpy_float_pow_oracle.npy |
| 2026-08-16 | full | test_nilpy_pow_matches_cpython.npy — red, fixed same day |
Each was worked within a day, while the float tickets sat at p15-p30 untouched.
That is the door the prio: field cannot reach, open and in use.
What was fixed: the MECHANISM, at zero coverage cost
The three shapes proposed all change what the tiers assert. This changes what a red says. A red fires at full strength and now arrives carrying its subject, in the line the triager is already reading:
FAIL test-nilpy#23 unit 4.6s test/x.npy
^ SUBJECT float-accuracy: last-digit float accuracy — LOW PRIO by
the owner's standing rule (devdocs/progress/float/README.md); do
not let this outrank a crash, a hang or a wrong-at-scale value. A
NaN/Inf fault, a saturation or a crash in the same file is NOT
this and stays full strength.
Same in tstate/reports/*.md, per red rather than only the first.
A subject is DECLARED by the test, never inferred — # PXX-SUBJECT: float-accuracy in .npy/.py, { … } in .pas, /* … */ in .c. Inference
from a filename would have labelled test_nilpy_math_domain_errors.npy as
float-accuracy when its subject is NaN/Inf handling, i.e. the exact test the
escape rule says must stay full strength. Same error shape as exempting a job
because its recipe invokes xvfb-run: reading the surface gets the motivating
case backwards. An undeclared job reports "", which means the test did not
say — never not float.
Guard: testmgr_job_subject_devtest.py (12 cases, including the
float-sounding-filename trap and the no-lines partial job, because
report_job runs while reporting a failure and must never raise). 76 guards
green.
Why no test was marked, and why that is the finding rather than a shortfall
The intent was to ship a declared list alongside the mechanism. Reading the five recurring offenders makes clear that no defensible file-level list exists:
| test | its actual subject |
|---|---|
math_domain_errors |
NaN / -Inf / exception behaviour. The escape rule excludes it outright. |
math_log |
a missing name — math.log was undefined variable (log). CLAUDE.md is explicit that a missing function a working CPython program calls is NOT F. |
pow_matches_cpython |
precision — but it guards 84 ulp, not one. Its own header: "78 exact before, worst error 84 ulp; 104 exact after, worst 1 ulp". |
float_pow_oracle |
differential against CPython; same span. |
str_float |
str(3.14), str(-1.25) — short exact decimals, where a move is a formatting bug, not last-digit noise. |
Every candidate file mixes last-digit accuracy with something that must stay full strength. That is a fact about the tests, and it decides the shape:
- Shape 2 (move them off the gating tiers) is wrong. It operates on whole
files, so it cannot draw the line the ticket itself requires — de-gating
pow_matches_cpythonde-gates an 84-ulp regression, and de-gatingmath_domain_errorsde-gates a NaN fault. The ticket's own test — "if a shape cannot draw that line, it is the wrong shape" — rejects it. - Shape 3 (regenerate from pxx) remains the worst, unchanged.
- Shape 1 (tolerance) is the right granularity, because the line runs
through these files rather than between them: per assertion, not per file.
It is also the most work, and it needs the owning lane — these are
.npytests, Track N's files, and N knows which assertion is pinning a rendering and which is pinning a contract.
The labelling landed here is the part that was safe to do unilaterally and costs nothing. Whether it is sufficient is now an empirical question with a cheap answer: the next float red carries its note, and either it gets triaged at its subject's priority or it does not.
Still open
- Adoption — the marker is a one-line, reviewable change per test, by the lane
that owns it. Track N for the
.npyset above. - Shape 1, if labelling proves insufficient, at per-assertion granularity.
Deliberately NOT done: no float behaviour touched, no test de-gated, no expectation regenerated, no coverage removed.
2026-08-28 — the empirical question has an answer, and item 1 is NOT
adoptable by any of the files that motivated it
The 2026-08-26 entry above ends with a falsifiable proposal: "the next float red carries its note, and either it gets triaged at its subject's priority or it does not." Measured today, both halves of that are empty.
Measurement
| question | answer |
|---|---|
tests declaring PXX-SUBJECT anywhere in the corpus |
0 |
| runs recorded since the mechanism landed (2026-08-26) | 259 |
| float-named red / still-red / fixed events in them | 0 |
So the mechanism has never fired, for two independent reasons: no test declares a subject, and no float red has occurred to carry one. A label with zero adopters is indistinguishable from no label — the same shape as the rest of this ticket, one level in: a signal applied nowhere is not a signal.
Why adoption is empty by construction, not by neglect
job_subject() reads the first 4096 bytes of a job's sources and returns one
subject for the whole job. That is file granularity — precisely the
granularity this ticket used to reject shape 2:
Shape 2 (move them off the gating tiers) is wrong. It operates on whole files, so it cannot draw the line the ticket itself requires.
And the same entry's own table shows the line runs through every motivating file, not between them. Applying the shipped marker to any of the five would do the harm shape 2 was rejected for:
| file | marking it float-accuracy would de-prioritise |
|---|---|
math_domain_errors |
a NaN / -Inf handling fault — excluded by the escape rule outright |
math_log |
a missing name (undefined variable (log)) — explicitly NOT F per CLAUDE.md |
pow_matches_cpython |
an 84 ulp regression, not one |
float_pow_oracle |
the same span, differentially |
str_float |
str(3.14) — a formatting bug, not last-digit noise |
So "still open, item 1: adoption by Track N" is not work waiting to be done — it is work that this ticket's own reasoning forbids. Filing it at N would ask that lane to make five changes each of which de-gates something the escape rule protects. Item 1 is closed as not adoptable, not as done.
What the marker remains good for is a future test whose subject genuinely is last-digit accuracy end to end. That is a real if modest value, it costs nothing, and it stays.
What is actually left, and why it is a Track U call
The only remaining shape is per-assertion subjects — the granularity the 2026-08-26 entry already identified as correct and expensive. That is a genuine fork rather than a task, and it is not mine to settle:
- it is substantial machinery in T's tooling plus a per-assertion pass through N's files;
- its entire subject is float accuracy, which the owner has called low prio by definition, four times;
- and the urgency that justified it has not been observed in 259 runs.
Building significant infrastructure to manage the priority of the lowest-prio
subject in the repo is exactly the trade a lane agent should not decide alone.
Escalated as decide-t-per-assertion-subjects-or-accept-the-file-level-label.
Recommendation stated there: accept the file-level label as future-only and build nothing more, revisiting only if float reds resume at the historical rate.
Deliberately NOT done, again: no float behaviour touched, no test de-gated, no marker applied to a mixed-subject file, no coverage removed.
Deprioritised 2026-09-02 — the Track T tooling backlog was cut as a pile
This ticket is not being called wrong. It was moved as part of a pile, not judged individually, and nothing here disputes its finding.
Owner decision. 73 of the 74 open track: T tickets were filed between
2026-08-31 and 2026-09-02, 58 on one day. The pile was too large to work through
and returned almost nothing, and a ticket nobody will fix does not sit neutrally
— it stays in the ranker forever at zero value, which is the argument CLAUDE.md
already makes for a terminal folder over a low prio.
Four were kept in the ranker on a purely structural test — an active umbrella or
a hard blocked-by: edge from live work:
umbrella-one-full-tier-run-with-no-red-tier,
feature-t-freebsd-image-and-runner, and the two regression-test-core-* reds
that block the umbrella.
Kept, not deleted, for two reasons: so the finding is not rediscovered and refiled from scratch by the next agent who trips over it, and so it can be pulled back if what it touches becomes load-bearing.
To revive it: move it to the owning lane's backlog, set status: backlog,
and say in the ticket WHAT CHANGED to make it matter now. Restoring it because it
reads well is how the pile comes back.