0007 — Pipeline principles and simplification¶
- Status: Proposed
- Date: 2026-10-18
- Plan:
docs/superpowers/plans/2026-10-18-pipeline-simplification.md
Context¶
ClinVar-GKM exists to give the public community reliable, high-quality ClinVar data that significantly lowers the barrier to understanding, access, use and interoperability. The pipeline has to transform ClinVar into the GA4GH GKM schema set (VRS, Cat-VRS, VA-Spec) and stay maintainable by a small team, with as few points of failure as possible.
Size today (main @ 567272c; src/ is identical on 1.0 @ 6606d3d):
| Part | Count | Lines |
|---|---|---|
BigQuery procedures and functions (src/procedures/*.sql) |
22 files | about 9,300 |
Release scripts (src/scripts/, top level) |
31 files | about 5,000 |
Parquet export SQL (src/scripts/parquet-schemas/*.sql) |
21 files | 193 |
Oracle and validation scripts (oracle-*.sh, validate-*) |
10 files | — |
The 1.1.0 work found defects that fall into four classes. None of them is a one-off; each class comes from how the pipeline is built.
- Duplicated logic that drifted apart.
- The Parquet SQL re-derives every table from the BigQuery
gkm_dict_*tables, so it had to copy the bundle assembler's cleanup. The copy was incomplete: typedextensionsinscvandevidenceLineParquet carried all-null placeholder elements that the bundle does not have (#143). - Two null-strip implementations exist: the assembler's
strip_emptyand BigQuery'sJSON_STRIP_NULLS. ApplyingJSON_STRIP_NULLSto the key/value tables would have broken the VRSRangenulls in 65,964locationrows (#143). - The proposition type → bundle section mapping is copied in 11 places: 8 procedure sites, the NDJSON export, the delta manifest and the conformance validator (#155 §2.3). The copies already differ in detail.
- The Parquet SQL re-derives every table from the BigQuery
- Mixed reference forms. Bare CURIEs and
#/section/keypointers were both used for references: 7,811,379 bare values in VCV propositionobjectandvcv-ConceptSetconceptson 2026-10-11 (#160). The published 1.0.1 Parquet held JSON-quoted pointers invartherapy-proposition.object_therapy("\"#/therapy/clinvar.therapy:…\""), fixed in #143. Consumers and our own Parquet SQL strip pointers with regexes in many places. - Incremental and full builds disagreed.
- Unordered aggregates made the procedures non-deterministic: two runs of unchanged SQL gave different
fingerprints for
gkm_dict_variation, the VCV/RCV aggregate tables,variation_hgvsandvariation_identity(#161). - A duplicate allele key flipped between builds, so it showed up in weekly deltas (#157).
- The VCV/RCV carry-forward parses the accession out of the proposition id (
SPLIT(key, '-')), which ties the delta model to the id format (#155 §3.B).
- Unordered aggregates made the procedures non-deterministic: two runs of unchanged SQL gave different
fingerprints for
- Upstream inputs trusted without checks.
clinvar_ingest.entrez_geneis missing 64,854 gene ids thatgene_associationuses.rcv_mappinglists SCVs that are absent from the VCV data, 2–3 a week. It is a faithful parse of ClinVar's RCV XML, which runs out of sync with the VCV XML. One stale SCV left four RCV propositions with noobject(#158).- ClinVar's VCV XML carries 16 malformed HGVS expressions with no sequence-type letter (#145).
single_gene_variationcounts gene-association rows instead of distinct genes, so 55 haplotypes and genotypes whose alleles share one gene get no gene.
Each fix so far has been local. Without a shared standard, the same classes will recur.
Decision¶
These five principles are normative. Every change to the pipeline or the published data is measured against them. A change that departs from one says so in its PR, with the reason; a lasting departure needs its own ADR.
- ClinVar fidelity. Never alter or drop source data silently. A repair keeps the original value in an
extension, and an inferred value is marked as inferred. Examples:
originalClinvarExpression/invalidClinvarExpression(#145),originalClinvarCitation(#156),geneContextSource(#159), andsubmittedConditionkept as the submitted CURIE. - One source of truth. The JSON bundle is canonical. Every other published artifact (Parquet, lookup files) must be recomputable from the published bundle. Derived columns, join keys and lookup tables are allowed. A new fact goes into the bundle first and flows out from there.
- Conformance on real artifacts. Validate the actual published bundles, full and delta, with the GA4GH gkm-toolkit against the strict schemas. A hand-built sample or a sub-bundle does not count: it can mask export and assembler bugs.
- Determinism. The same input gives byte-identical output, and incremental output equals a full rebuild. Every aggregate, pick and array order that reaches an output uses a total order.
- Change published data only by rebuilding. One consolidated rebuild and backfill per release, with the docs updated in lockstep. Published files are never patched in place.
Rules these principles consolidate¶
These rules already exist. The principles state the intent behind them; the detailed rules stay where they are.
| Principle | Existing rule | Where it lives |
|---|---|---|
| 1 | ClinVar's submitted text is never altered or dropped | #144 plan, decision A1; #145 |
| 2 | The assembler (assemble-gkm-dicts.py + gkm_json_cleanup.py) is the single publish-layer cleanup point |
CLAUDE.md; scripts.md "Publish-layer cleanup & bundle conformance" |
| 2 | schema/clinvar-gkm/ is the source of truth for the schema |
ADR 0004 §9 |
| 3 | Validate the real full and delta bundles with gkm-toolkit; sub-bundle.py output is not proof |
CLAUDE.md; scripts.md "Conformance validation" |
| 3 | Validate the data a procedure produces, not just that the SQL runs | sql-procedures.md "Cat-VRS variant/constraint shapes" |
| 4 | Incremental procedures are byte-identical to a full rebuild; total-order ORDER BY; no ANY_VALUE for values the group key does not determine |
sql-procedures.md "Determinism / correctness"; ADR 0001 |
| 4 | Full-vs-incremental and delta-reconstruction oracles report 0,0,0 before publishing |
sql-procedures.md |
| 5 | Settle every data decision, then run one consolidated rebuild and backfill; docs move with every PR | maintainer rule, applied in #144 and #155 |
| 5 | Never overwrite a published dated path without a CDN purge; every live R2 write is confirmed by a maintainer | scripts.md "Cloudflare R2"; ADR 0005 |
Consequences¶
The principles point the pipeline toward fewer moving parts. The simplification plan has the detail, the measurements and the decisions still needed. In summary:
- Generate Parquet from the bundle output. Build the Parquet tables from the cleaned records the assembler writes, instead of re-deriving them from the dict tables in 21 SQL files. The copied cleanup and the placeholder class of bugs go away (principle 2).
- One canonical definition per concept. Pointer formatting, pointer stripping, the type → section mapping and the list of tracked tables each get one shared definition (principles 2 and 4).
- Re-evaluate the incremental machinery with cost data. Measure each procedure in full and incremental mode
over several weeks before deciding where carry-forward pays for its complexity. The delta product does not
depend on it:
gkm_change_logalready finds changes by diffing outputs (principle 4). - Input contract checks at the upstream boundary. Check the inputs before each run. A violation fails the run or is logged; it is never silently absorbed (principle 1).
- One release gate command. The oracles, strict gkm-toolkit validation of the real bundles, the bundle-vs-Parquet recompute and the reference guard run as one command that blocks publish (principles 3 and 4).
- Restraint on new published artifacts. Every new artifact needs a stated owner and gate, and derives from the bundle or the published Parquet (principles 2 and 5).
Other consequences:
- The in-flight 1.1.0 release already applies parts of this: the canonical type → section function (#155),
uniform
#/references with a bare-reference guard (#160), the determinism fixes (#161, #157) and the release gates in #155 §6.4. The simplification work starts after 1.1.0 ships. - Principle 5 retires the patch-style backfill used for the 1.0.0
conditionSetrepair and prepared for 1.0.2 (#144). #155 had already moved 1.1.0 to a full rebuild. - Rebuilds cost more than patches. The #155 estimate for a full rebuild of all 16 datasets is about 3.4 TiB billed, about $21 at on-demand rates. That is small next to the cost of an unverifiable patch.
- Reviewers gain a checklist: which principle does a change touch, and which gate proves it holds.
- Once accepted,
CLAUDE.mdshould point to this ADR from its project context section.