@@ -820,16 +820,12 @@ reads. `build_libraries` already prefers aligned over FASTQ per sample, so the t
820820deciding it again here means a manifest can never list a file the data kind says the node will not
821821open, and the download budget cannot be inflated by files nobody fetches.
822822
823- ** The hole: ` est_bases ` is ` NULL ` for essentially every unit.** It is ` reads × read_length ` , and the
824- crawl sets ` read_length ` to ` None ` — ENA's ` filereport ` does expose ` base_count ` , but
825- ` du-external ` 's ` RUN_FIELDS ` does not request it and ` genomics.sequence_library ` has nowhere to put
826- it. So the ** per-Gbp term of the credit formula (§6.3) has nothing to weigh a FASTQ unit by** .
827-
828- A byte-derived estimate was the tempting fix and is the wrong one: a fabricated number in a ledger
829- that pays people is worse than an honest null. So curation publishes the null and the job * warns*
830- with a count, rather than leaving a silent hole. ** The fix, before credit goes live:** add
831- ` base_count ` to ` RUN_FIELDS ` , carry it through ` crawl_project ` , and store it — either as a column on
832- ` sequence_library ` or in the ` atproto ` provenance slot that already holds ` run_accession ` .
823+ ** The hole it found — now CLOSED, see [ §12.8] ( #128-closing-the-est_bases-hole ) .** ` est_bases ` was
824+ ` reads × read_length ` and the crawl never set ` read_length ` , so it was ` NULL ` for essentially every
825+ unit and the per-Gbp term of §6.3 had nothing to weigh a FASTQ unit by. A byte-derived estimate was
826+ the tempting fix and the wrong one — a fabricated number in a ledger that pays people is worse than
827+ an honest null — so curation published the null and warned. The real fix was to ask ENA for the
828+ figure it had been publishing all along.
833829
834830### 12.6 Validation, and two contracts that did not need to exist
835831
@@ -910,13 +906,49 @@ position a contributor *would* hold, but to a node with its first unit still in
910906as "you are last" — a discouraging answer to a question nobody asked, about a board the contributor
911907does not yet appear on. ` null ` means unranked, which is what is true.
912908
909+ ### 12.8 Closing the ` est_bases ` hole
910+
911+ By the time ` grid-validate ` shipped, §12.4's gap had stopped being theoretical: the validator pays
912+ ` BASE_CREDIT + per-Gbp ` , so with ` est_bases ` null a 90 Gbp realignment earned exactly what a CRAM
913+ passthrough earned. The ledger would have gone live quietly wrong.
914+
915+ ** ENA had the figure all along.** ` filereport ` publishes ` base_count ` on ` read_run ` ; ` RUN_FIELDS `
916+ simply never asked for it. It now does, and migration ` 0076 ` adds
917+ ` genomics.sequence_library.base_count ` to hold it.
918+
919+ ** A typed column, not the ` atproto ` JSONB slot** — which was the other option, since that slot
920+ already carries ` {source, run_accession} ` for crawled runs. But that slot is * provenance* : where a
921+ row came from. ` base_count ` is a measurement of the library, the same kind of fact as the ` reads `
922+ and ` read_length ` columns beside it, and it is summed in an aggregate that feeds a ledger paying
923+ real people. In a JSON blob a missing key reads identically to a zero; in a typed column a NULL is
924+ visible. That distinction is the whole point here.
925+
926+ ** It is the measured total, not the product.** ` est_bases ` now prefers ` base_count ` and falls back
927+ to ` reads × read_length ` only where a row predates the column. The product is a mean-length
928+ approximation and is wrong outright for variable-length reads — which is to say for every long-read
929+ platform the Grid will see.
930+
931+ ** Old rows needed a job, not a re-crawl.** ` sequence::ingest_libraries ` is idempotent at * sample*
932+ granularity and skips a sample that already has files. That property is what keeps re-crawls cheap,
933+ and weakening it for one column would be a bad trade. So ` run-once ena-base-count ` fills the column
934+ directly, one run at a time — ` filereport ` filters on whatever accession it is given, so a run
935+ accession returns just that run — in bounded batches with the same politeness gap as the study
936+ crawl. Re-run until it reports nothing examined.
937+
938+ Two properties made explicit rather than left incidental, both about not corrupting a ledger:
939+
940+ - ** The backfill only ever fills a NULL.** Re-running it cannot overwrite a measurement and silently
941+ change what a contributor was already paid for.
942+ - ** An empty ` base_count ` from ENA leaves the NULL.** A submitter who never supplied the figure is
943+ not the same as a run that sequenced nothing, and writing a zero would conflate them.
944+
945+ Where no honest figure exists the null survives and ` grid-curate ` still warns with a count. That was
946+ right before and is still right; this only makes the null rare instead of universal.
947+
913948### What is NOT yet built
914949
915950` Provenance ` in ` du-domain ` (§5.1) and the whole Navigator edge (` ena.rs ` , ` grid.rs ` , the driver,
916- the ` contribute ` CLI). Plus the ` est_bases ` gap in §12.4, which ** must close before credit goes
917- live** — ` grid-validate ` already pays ` BASE_CREDIT + per-Gbp ` , and with ` est_bases ` null every
918- FASTQ unit pays the base only, so a 90 Gbp realignment currently earns exactly what a passthrough
919- does.
951+ the ` contribute ` CLI).
920952
921953### 12.5 What the reaper is actually for
922954
0 commit comments