Parity before improvement¶
Status: in force for the fetch10xmeta replacement ·
Evidence: the parity record
The decision¶
fetch links reproduces fetch10xmeta's nine-step priority chain
deliberately, including the places where this project's own
file layer would choose differently.
A swap that changes behaviour is not a swap. The pipeline downstream —
WGET10X first — reads a five-column links.tsv with a ORIFQ/ENAFQ/BAM/
SRA vocabulary, and the moment that file changes shape or content, a
replacement stops being a replacement and becomes a rewrite with an unmeasured
blast radius.
Result: 14 of 14 test cases, no differences — 422 snapshot rows covering 400 distinct runs, plus one case (E-MTAB-8060, 15 rows) that has no snapshot and passes on assertions alone (the count).
The contract is the incumbent's own test suite¶
Not a specification written for the occasion.
modules/cellgeni/fetch10xmeta/tests/main.nf.test.snap records, per case, the
(run, species, type, sample) tuple for every run, and
scripts/parity_fetch10xmeta.py reads that file directly rather than carrying a
copy — so the comparison stays true as the incumbent's tests change.
The snapshot omits the URLs, because SDL hands out signed links that rotate. That omission is what makes it the right comparison: it asks which files were chosen, not which mirror served them.
Where the two would disagree, and why parity still holds¶
The incumbent decides ENAFQ by requiring both _1.fastq.gz and
_2.fastq.gz. That is a name test standing in for a completeness test: it
rejects a genuinely single-end library along with a truncated paired one.
This project reaches the same verdict from library_layout, which is the fact
rather than a proxy for it — and catches the
24 genuinely single-end libraries
in the same sample that the name test also rejects.
The two agree on every case the snapshots cover, so parity holds while the better mechanism ships underneath. That is the only shape in which "improve it" and "do not change it" are both satisfiable.
What is deliberately not reproduced¶
Steps 8 and 9 — the SRA table's archive URL, then a srapath shell-out.
Both ask the SRA toolkit for a signed, rotating link, which is a download-time
concern rather than a resolution result. The incumbent's own snapshots exclude
those URLs for the same reason.
The intermediate files. *.list, *.txt and *.soft were by-products of a
shell pipeline; nothing downstream consumes them. relations.tsv replaces them
with the part that makes a failure diagnosable afterwards — which route supplied
each field, and where two archives disagreed.
errorStrategy = { task.attempt == 5 ? 'ignore' : 'retry' }. Ignoring on
the fifth attempt drops the dataset silently: the pipeline continues, the sample
is simply absent, and nothing downstream reports it. That is how datasets go
missing without anyone noticing. The replacement uses 'retry' and lets a
genuine failure fail.
The stub's file names. The incumbent's stub: block writes
${prefix}.sample.list and ${prefix}.accessions.tsv, which its real script
never produces. A stub that disagrees with its process tests nothing.
Three defects on our side had to be fixed to reach parity¶
Worth recording, because each was a case where this project resolved fewer runs than the bash module it is replacing — and none was visible without the comparison.
- ArrayExpress was not a working entry point. ENA's
filereportrejects anE-MTABaccession with a 400 naming the eight shapes it accepts. The 400 was caught, logged and swallowed, so every ArrayExpress accession resolved to a series with no runs. - The GEO sample was lost where GEO omits the SRA relation. GSE135325 and GSE137444 record a BioSample per sample and an SRA relation for none, so every row carried the INSDC sample instead — a different identity from the one a reprocessing pipeline is asked about. 36 of 36 and 50 of 50 recovered.
- The SDRF was read through a dict. SDRF repeats
Comment[FASTQ_URI]once per mate andcsv.DictReaderkeeps only the last. E-MTAB-9221 yielded 20 URIs for its 20 runs instead of 40 — half the data, no error. Caught only by checking against the 40 files the study registers.
What would end this¶
The swap landing. Once FETCH10XMETA calls the container and the incumbent's
bash is deleted, the parity contract has served its purpose and the file layer's
own ranking becomes the one that ships. Until then, changing links.tsv moves
the target.
That is also why CLAUDE.md says not to "fix" the incumbent's bash from this
repository: its behaviour is the contract.