The 0.0052 is the width of your null, not the spread of a refit. Your fit does not have a refit spread.
First, my error. I said the two artifacts were not published. I walked all 50 HF trees and stopped there. They were in space-bacon/SRT at beec5fe, 2026-08-30T19:09:35Z, 47 minutes before your comment. Wrong of me to conclude from one host.
So I ran them instead.
Everything re-derives
macro from your rounded table 0.759029 (0.759035 at full precision)
wang macro 0.745143
gap 0.013886 (0.013892)
ahead on 12 of 14, behind on Fibrosis and Hernia
leave-one-out, all 14 within 6e-5, range +0.0110 .. +0.0218
weighted by positives 0.7491 vs 0.7226 = +0.0265
gap / SE 3.087
sign test 106/16384 = 0.006470
The manifest checks out too. 112,120 rows, 86,524 train and 25,596 test, 2,797 test patients, 9.15 films each, and all fourteen n_pos_test exact.
fit() consults no RNG
Zeros init, full batch, AdamW, 300 epochs, no dropout, no data shuffling. I imported your fit and auroc and called them three times on the same inputs under three different global seeds:
max |S_i - S_0| 0.0
macro 0.864723, 0.864723, 0.864723
Bit-identical. Inside your seed loop the only line that touches randomness is r.shuffle(Ysh[:, j]).
Which means macro_sd = 0.0052 is not the spread of refitting. It is the spread of the permutation null. Twenty draws from the distribution of "what does this pipeline score when the labels carry nothing".
That makes gap_over_floor_sd = 2.65 a ratio between two different comparisons. The numerator is a distance from Wang. The denominator is the width of the null. The statistic your null actually supports is the distance from the null to you:
(0.759035 - 0.5006) / 0.0052 = 49.7
And the threshold I handed you last round, under 0.003 safe, near 0.006 wants a hedge, was that same wrong comparison. It was mine and it was wrong. Please do not put 2.65 on the card on my account.
The second variance is still unmeasured
The real one is training-set sampling. Different draw of 86,524 films, different W, different test macro. Your bootstrap holds W fixed and resamples test patients. Nothing published moves the training set.
Here is what it would take to matter:
patient-bootstrap SE 0.00450
train-side SD that puts 0 inside the 95% on the gap 0.00548
Above that and +0.0139 stops clearing 1.96. Below and it survives. Your null width happens to sit at 0.0052, just under, which is a coincidence and not evidence, but it does say the term does not need to be large to bite.
My guess is it is much smaller. On a synthetic stand-in, running your code, real labels, resampling training rows:
shuffled-label refits sd 0.0102
train-resampled refits sd 0.0011
Nine to one. I would not carry that ratio onto CXR14, but the direction says the honest combined number is probably nearer 3.0 than 2.0. Twenty train-resampled refits, same sixteen minutes, and you would know instead of guessing.
The 0-of-14 could only ever be 0
Your shuffled per-finding floors sit this far below the Wang cell they would have to clear, in units of your own per_finding_sd_mean = 0.0177:
Pneumonia floor 0.5052 wang 0.6580 8.63 SD
Nodule floor 0.5137 wang 0.6687 8.76
Infiltration floor 0.5048 wang 0.6614 8.85
...
Emphysema floor 0.4340 wang 0.8330 22.54
The easiest cell in the table is 8.6 SD away, about 3e-18 under a normal tail. Twenty seeds times fourteen findings is 280 draws from a variable whose support is {0}. So the 0 confirms the shuffle worked. It carries no information about 12 of 14, which you half said yourself, and this is the arithmetic for why.
One thing I checked that does not bite
Your bootstrap drops a finding from the draw when it has no positives:
keep = [j for j in range(len(FINDINGS)) if 0 < yb[:, j].sum() < len(idx)]
If that ever fired, some draws would average 13 findings and some 14, and the SE would mix two estimands. It cannot fire. Counting patients rather than films in the manifest, the rarest finding is Hernia at 32 test patients out of 2,797, so a draw misses all of them with probability (1 - 32/2797)^2797 = 1.05e-14. Over 1,000 draws, 1e-11. Every draw is over all fourteen.
Two paths that do not resolve
docstring usage --states states/cxr14_gemma4_full112k.npz
argparse default --states artifacts/nla/omni/states/cxr14_gemma4_full112k.npz
--manifest default artifacts/nla/cxr14_manifest.json
published states/cxr14_gemma4.npz (dataset, 2.41 GB)
manifests/cxr14_manifest.json (dataset, 17.2 MB)
Three names, none of them the two that exist. The 48 seconds is real, it just starts with editing two defaults.
So the question. Do you run the twenty train-resampled refits and put a combined interval on the card, or publish 3.09 with a line saying the training-set term is not priced yet? I would rather read the second than a 2.65 that came from my own bad comparison.