Changelog
All notable changes to this project are recorded in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
0.4.0 - 2026-10-16
Added
--protein_versionappends the translation version toENSP,--mask_header_cache_pathmasks the cache directory in the output headers, and--regulatory_gff,--extended_promotersand--custom_suppress_filterare refused, naming what each would do. (#44)- The plugin-visible
InputVariantJSON carriesinput_line_name,record_so_termandallele_num(the record’s input-line name, class term and printed ALLELE_NUM), andmate_idonly on a record naming a mate. (#44) - Comparators take
--reference-release {115.2,116.2}(default 116.2) with a registry of records Ensembl VEP drops or annotates differently; release pages tabulate whole-genome and structural-variant F1; documentation: https://natera-open-source.github.io/vep-rs/. (#43, #44, #50, #57)
Changed
- Ensembl VEP 116.2 is the reference release: headers and banner read
v116.2,--cache_versiondefaults to 116,tests/golden/116/replacestests/golden/115/, anddocs/intended-divergences.mdis rewritten against it. (#38, #44, #45) - Stop-codon consequences follow Ensembl VEP release 116: an edit beginning inside the stop codon is
stop_lostorstop_retained_variantwithoutframeshift_variant; indels decidestop_retained_varianton the genomic span. (#44) - Breakend records follow Ensembl VEP 116: an
intergenic_variantrow per allele when no transcript is reached, own breakendN.without MATEID at ALLELE_NUM 0,<BND>mates named through CHR2. (#44) - A multi-allelic structural variant takes one class from its joined ALTs and writes one row per ALT, each labelled with the record’s class term, as Ensembl VEP 116 does. (#44)
- Records Ensembl VEP 116 drops before annotation are annotated, the divergence the golden manifests class
reference_skipped_record, and--max_sv_size -1lifts the size limit in every output format. (#44) - JSON
cdna_end,cds_endandprotein_endare omitted when the end is undefined (445-?writescdna_startalone), and a structural record’sallele_stringcarries every ALT as written (Ensembl VEP 116). (#44) Uploaded_variationreads a VCF record’s first;-joined ID, and a record whose ID is., empty,0or absent is named from its input line (Ensembl VEP 116). (#44, #51)--hgvsfollows Ensembl VEP 116: a deletion past a transcript’s edge written over the bases inside it, a stop-codon frameshift as an extension, a stop-codon indel by its peptides. (#44)
Fixed
- Structural-variant records are routed, typed and spanned as Ensembl VEP’s parser reads them, in one module shared by the command-line parser and the
vep-ioreader; no coordinate wraps. (#44) <CNV:TR>alleles follow Ensembl VEP 116: the length reads RUC before RB and sums the RN sequences, and a repeat longer than--max_sv_size, or any under-1, stays symbolic. (#44)- A structural deletion or copy-number loss over the stop-codon window takes
stop_lostunless its first base in transcript order lies in the incomplete terminal codon (Ensembl VEP 116). (#58) --proteinfills ENSP on every structural-variant row of a translated transcript, and the## VEP command-lineheader keeps--max_sv_size -1and thekey=prefix of a masked--custompath. (#44)- JSON
idof a record whose first ID is empty or0is the generated name, and the header cache path without--json_cachefollows Ensembl VEP’s<dir_cache>/<species>/<version>_<assembly>layout. (#51) - The concordance harness passes
run_splice_predictions:0to Perl LoFTEE and keys its Perl output cache on plugin arguments, and--plugin LoFTEEwarns when asked for that unimplemented splice layer. (#47, #49)
0.3.2 - 2026-10-06
Changed
--fork 0(the default) uses every logical CPU up to 32 instead of every logical CPU: one reader feeds the worker pool, so the wall time stops falling past about 32 workers and rises on wider hosts (gnomAD v4.1 chromosome 2 took 1.8 times longer at 192 workers than at 32 on a 192-vCPU host). An explicit--fork Nis never capped.- A batch is annotated, filtered, rendered and freed in one pass over the worker pool, each finished variant freed by the thread that annotated it and the task size following the pool’s thread count, instead of five pool barriers per batch with the coordinator freeing every variant serially between batches. The output is byte-identical. Together with the libdeflate inflate below, measured against 0.3.1 under the same whole-genome protocol (the median of 20 machines per chromosome at 16 threads, summed over the chromosomes), the whole of gnomAD v4.1 falls from 974 to 724 s on ARM Graviton4 and from 1,219 to 1,169 s on x86 and the whole of gnomAD v2.1.1 from 586 to 282 s and from 835 to 476 s, with CPU time down 19 to 44 percent on those inputs and peak memory at most 2.4 GB on any chromosome against 4.9 GB.
- bgzip blocks inflate through libdeflate (
noodles-bgzfwith itslibdeflatefeature), and the cache builder’s MySQL client takes its Rust deflate backend so that no crate of the workspace links C zlib: the single-inflater floor on gnomAD v4.1 chromosome 21 falls from 73.8 s to 34.5 s.
0.3.1 - 2026-10-02
Added
- The SNP/indel comparator applies the transcript-selection class of
docs/intended-divergences.md(section 3) to the records of a whole-genome input that Ensembl VEP marks skipped, under the structural-variant comparator’s two guards, and reports it assv_transcript_selection; the scope test is defined once and shared by the two comparators.
Fixed
Each entry names the Ensembl VEP mechanism vep-rs follows.
- A symbolic insertion whose
ENDis its position or whoseSVLENis 0 is the insertion between the two bases,start = POS + 1, end = POS, as ensembl-io’sget_endreads it: its Location names both flanks, its predicates read the pair as_intron_effectsreads a sequence insertion (both flanks inside a region, the two insertion special cases at the intron edges, the UTR special cases at the coding region’s edges,within_cdnathroughmap_insert), and the row carries noOverlapBPorOverlapPC(an overlap length of 0). A symbolic record with neither field stays the single base atPOS + 1, which is also ensembl-io’s reading. - UTR term on an insertion at the edge of the coding region:
_before_codingand_after_codingspecial-case an insertion whose start is the coding region start or whose end is the coding region end, so an insertion between the last base of an intron and the first base of a coding exon (or between the coding region’s last base and the intron after it) is5_prime_UTR_variantor3_prime_UTR_variantby strand beside its splice term, where it carried the splice term alone. - A frameshift intron on the UTR side of the coding region:
within_cdsrequires the span to overlap the coding region, and_bvfo_predssetsutrrather thancodingfor a span that does not, so a variant inside an intron of twelve bases or fewer that lies before the coding region is5_prime_UTR_variant(or3_prime_UTR_variantafter it) rather thancoding_sequence_variant. - Structural
stop_lostbehind thecodingpre-predicate: the term carriesinclude {coding => 1}, and_bvfo_predssetscodingonly for a span that overlaps the coding region and an exon, so a deletion inside the intron that splits a stop codon isnon_codingand carries nostop_lost, although the codon window drawn on genomic coordinates reaches into that intron. - No UTR term on a structural span that starts on the first base of a
transcript whose coding region starts there (
cds_start_NF, and likewise at the last base withcds_end_NF):_before_codingisoverlap(start, end, transcript start, coding_region_start - 1), an inverted window that admits only a span starting before the transcript.
0.3.0 - 2026-10-01
Added
- A container image,
ghcr.io/natera-open-source/vep-rs, for each published release from 0.2.0: the release’s attested x86_64 Linux binaries and theduckdbCLI on Debian 13, smoke-tested on the GRCh37 corpus before it is pushed, with a build-provenance attestation on the pushed digest (.github/workflows/docker.yml). docs/intended-divergences.md: every class the adjusted concordance sets aside, each with the record, both engines’ rows, the mechanism in Ensembl VEP’s source, and a command that reproduces it.- Structural-variant comparator masks for the second side of two classes and
for three more: the
intergenic_variantrow vep-rs writes for a record whose VEP tuples all name transcripts on another chromosome (filter_cross_chromosome_orphan_intergenic); the mate allele of a breakend above--max_sv_sizethat VEP never wrote, gated on the mate lying within 5 kb of the transcript span (filter_giant_breakend_mate_divergences); the SNP/indel registry’s excluding rules applied to structural pairs (filter_registry_swap_pairs); VEP’s batch-dependent Transcript tuples on a<NON_REF>record (filter_non_ref_batch_divergences); and a mate-side row VEP evaluated at the local coordinate (filter_breakend_mate_local_read_pairs), withscripts/validation/breakend_mate_context.pyderiving the mate-coordinate and local-coordinate readings from the JSON cache. The transcript-selection mask’s unsupported-type scope names<CPX>and<CTX>. - The golden classifier names
breakend_mate_local_read, computed by the same derivation over the corpus’s own pruned cache. - The comparators write the adjusted residual:
discordant_open.tsvbesidediscordant.tsvfor the SNP and indel sets anddiscordant_adjusted.tsvfor the structural-variant sets, each the one-sided rows every mask leaves in place. Per-class mode caches the per-term VEP totals of a reference output (--vep-totals-cache).
Changed
- JSON cache format: each transcript’s
variation_effect_feature_cachecarriesseq_edits, the translation’sBio::EnsEMBL::SeqEditlist (start,endandalt_seqin protein coordinates, withcode,nameandlength_diff), whichscripts/data/storable_to_json.plwrites from the Storable cache. vep-rs applies the edits overlapping a variant’s translation span to the reference allele’s peptide, asTranscriptVariationAllele::peptidedoes. A cache converted without the key still loads: no edit is applied, and a variant at codon 1 of a transcript with an alternative initiation codon is called from the cached peptide. Convert such a cache again to get VEP’s calls there. - The deletion-shaped evaluation of a same-chromosome breakend span borrows the variant for each overlapping transcript instead of copying it with the consequences accumulated so far, which grew with the square of the transcript count. Output is unchanged.
Fixed
Each entry names the Ensembl VEP mechanism vep-rs follows.
- Start codon on an alternative initiation codon: with
seq_editsin the cache, aninitial_metoramino_acid_subedit over a non-ATG start codon gives a referenceMagainst the alternate allele’s literal residue, so the variant isstart_lostrather thansynonymous_variantandAmino_acidsprints the edited residue. - Splice terms across a frameshift intron:
_intron_effectsskips an intron of twelve bases or fewer for every differing region that overlaps it, in both of its loops, so a deletion running from one exon across such an intron into the next carries no splice term; the coding predicates alone decide the row. The exonic splice windows apply the same skip, so a donor window that crosses such an intron yields nosplice_donor_5th_base_variant. transcript_ablationrequires Perl’sdeletionpre-predicate (the reference span longer than the ALT sequence) besidecomplete_overlap. A same-length substitution or a longer ALT covering a whole transcript takes the tier-3 predicates instead: the biotype’s context term with the UTR, intron and splice terms of the span.splice_polypyrimidine_tract_varianton structural alleles: a structural allele whose span reaches the 15 bp window inside an intron’s acceptor end carries the term unless the span touches an exon of the transcript, as Ensembl’s predicate does for every BaseVariationFeature. A<CNV:TR>allele keeps its endpoint test.- A paired breakend’s bracket allele gets a row for a transcript only when
the mate coordinate lies within
MAX_DISTANCE_FROM_TRANSCRIPT(5 kb) of it (StructuralVariationOverlap::_close_to_feature); the local POS is carried by the single-breakend allele. - A single-breakend allele above
--max_sv_sizethat partially covers a coding transcript takes the region predicates like every other same-chromosome span; VEP requirescomplete_overlap_featureforcoding_transcript_variantat any span. - The context term of a transcript a structural allele engulfs follows the
biotype, as VEP’s include hashes do:
coding_transcript_variantonly forprotein_coding,NMD_transcript_variantonly fornonsense_mediated_decay,non_coding_transcript_variantonly without a translation, andintergenic_variantfor a translated transcript of any other biotype. One rule serves the inversion, copy-number, insertion and breakend arms. - Structural insertions, mobile-element insertions and duplications whose
span covers a start-codon base with both ends in exons emit
start_lost, the genomic-overlap predicate the deletion arm applies.start_retained_variant, which VEP co-emits there without reading sequence, is not emitted. - The mate-side Transcript row of a paired breakend describes the mate
breakend at the mate coordinate:
feature_truncationwith the region term when the mate lies inside the transcript,upstream_gene_variantordownstream_gene_variantwith the distance when it lies within the window outside, and no row beyond it. VEP evaluates every predicate butfeature_truncationat the local coordinate there. - A gVCF
<NON_REF>record is a reference-confidence block, not an alternate allele. It is written as oneintergenic_variantrow spanning POS+1 to END with the allele as written andIMPACT=MODIFIER, with a warning per record; that is the row VEP writes when no other record in the batch loads the region. A<NON_REF>carrying a supportedINFO/SVTYPEkeeps that type.
0.2.0 - 2026-09-29
Fixed
- HGVSp is Perl VEP’s
hgvs_protein, ported whole. In-frame insertions between codons, duplications, stop-loss extensions (extTer{n}andextTer?), insertions carrying a stop codon and deletions shifted at the peptide level print VEP’s string where they printed nothing or a different one. The coding gate is read from the allele as annotated and the frameshift test from its 3’-shifted span, as VEP reads them, and the transcript peptide asTranscript::translatewrites it (Met1for any start codon of the table). The one intended difference: on a variant that keeps the start codon intact and is reported asstart_retained_variant, VEP printsp.Met1?from its ownstart_lostcall while vep-rs prints the peptide change. - HGVSc is Perl VEP’s
hgvs_transcript, ported whole: the 3’ shift within 1,000 bases of flank, the variant typing and duplication lookup ofhgvs_variant_notation, allele clipping and_get_cDNA_position. Deletions that cross an exon boundary or the start or stop codon, reverse-stranddelinsranges, insertions without a readable reference and variants outside the transcript span now match VEP. HGVS_OFFSETis emitted besideHGVScandHGVSpwhen an insertion or deletion was shifted, signed by the transcript strand as VEP signs it.
Added
- A GRCh37 chromosome 21 golden corpus generated with
--hgvs(tests/golden/115/GRCh37-hgvs/), carrying the complete chromosome 21 reference so the golden tests compareHGVSc,HGVSpandHGVS_OFFSETagainst Ensembl VEP’s output in every format with no external file. - Release measurements beside the paper’s: the README’s “Current release”
section states the released version’s ten-suite concordance and its wall
times (20 independent machines per cell on both architectures) beneath the
paper’s tables, which stay as published, from the release’s provenance
record under
docs/concordance-provenance/.
Changed
- Releases: the workflow creates a draft that a maintainer publishes, with a
body rendered from the repository’s files (
scripts/release/render_release_notes.py); the archives arevep-X.Y.Z-<target>.tar.gz, each unpacking to its own directory; agit archivesource tarball ships with its.sha256; oneSHA256SUMScovers every archive and every file in it carries a build provenance attestation (gh attestation verify);MD5SUMSis not published.workflow_dispatchruns the same build as a dry run without a release. - Dependencies: noodles 0.109 (the last release declaring rust-version 1.88;
the bgzf readers and writer and the tabix index loader moved to their
ioandfsmodules), mysql 28 (vep-cache-builder), rand 0.10 (vep-cache-converter), criterion 0.8 (benches), rustls 0.23.45, and the minor and patch releases of bigtools, bstr, clap, crossbeam-channel, flate2, indexmap, rayon, rustc-hash, serde, serde_json, smallvec, tempfile, thiserror and tracing-subscriber. The declared rust-version stays 1.88. - Workflows: every action is pinned to a commit sha with its tag beside it,
each rust-toolchain step names its Rust version through the
toolchain:input, and the CI workflow’s token is read-only. Dependabot ignores noodles 0.110 and later (rust-version 1.89) and the pinned toolchain refs. - Golden corpus provenance records the digest of the released 0.1.0 binary that classified the manifests.
- The measurement harness (
scripts/concordance/run_clone_measurement.sh) flushes and deletes the discarded warmup’s output before every timed run, so the timed run starts with the memory the warmup had; the warmup’s pages otherwise had to be reclaimed while the timed run wrote its own, and that reclaim landed in the timed wall time while user and system time stayed unchanged.
Security
- Cleared from the dependency tree: RUSTSEC-2026-0235 (rkyv),
RUSTSEC-2026-0173 (proc-macro-error2), RUSTSEC-2026-0002 (lru 0.12) and
RUSTSEC-2026-0097 (rand 0.8), all through the mysql and rand updates, and
RUSTSEC-2026-0285 (rustls) through the rustls update. None was reachable
from the
vepbinary; all sat under vep-cache-builder or vep-cache-converter.
0.1.0 - 2026-09-22
First public release.
Added
vep, a from-scratch Rust implementation of the Ensembl Variant Effect Predictor algorithm. It reads a JSON transcript cache converted from the Ensembl VEP cache, accepts VCF, Ensembl default, HGVS and region input, and annotates SNVs, indels, MNPs and structural variants (deletions, duplications, insertions, inversions, copy-number variants and breakends) with Sequence Ontology consequence terms, HGVS notation, protein predictions and co-located variation.- Output in the four Perl VEP formats (VEP default, VCF, JSON, Tab), each
field for field Ensembl VEP 115.2’s for the same input and flags, for every
row outside the keys the golden manifests list as divergent (header lines carrying a time,
a path or Perl API component versions excepted), plus Parquet, a vep-rs
extension: five key columns (
chrom,pos,end,ref,alt) then exactly the tab columns,-stored as NULL, Parquet v2, ZSTD 9, sorted row groups, a dictionary and a Bloom filter on every column, and the run’s identity in the footer metadata. - Golden corpora under
tests/golden/<release>/<assembly>/: per Ensembl release and assembly, exemplar records for every consequence-set combination observed on the benchmark datasets, Ensembl VEP’s output for them in the default, tab, VCF and JSON formats, a pruned JSON cache, and a manifest of the consequence keys documented to differ.crates/vep-cli/tests/golden.rsandformat_parity.rscompare every column of every format against them on eachcargo test, and the Parquet output round-trips throughscripts/adapters/parquet_to_vep_tab.pyto the tab output. Generator and pruner underscripts/golden/. scripts/concordance/compare_vep_fields.py: per-column and per-Extra-key agreement between two default-format outputs over their shared consequence keys, written asfields.jsonbeside every F1 report the measurement harness produces.--max_sv_size(default 10,000,000): as in VEP, a wider structural variant keeps its VCF line without consequences and is absent from the JSON output; the default and tab formats list every transcript it overlaps.- Parallel annotation with
--fork N; output is deterministic and identical at every--forkvalue. - Built-in plugins for CADD, REVEL, gnomADc, AlphaMissense, dbscSNV, LoFTEE,
LoFtool and pLI on both GRCh37 and GRCh38, GWAS and SpliceAI on GRCh38, and
dbNSFP, built in but unsupported;
a binary annotation-store format for plugin data; and a dynamic-library
plugin ABI (
vep-plugin) for plugins built outside this repository. vep-cache-builder, which builds the JSON transcript cache directly from Ensembl source data (GFF3, protein FASTA, variation VCF, and with--gtfthe release’s GTF for thecds_start_NF/cds_end_NFtranscript attributes and the assembly and genebuild versions) without a Perl installation, andvep-cache-converter, which validates an existing JSON cache for runtime use and converts tabix-indexed plugin data to the binary annotation-store format. Every JSON cache carries itssource_versionsininfo.json.- A concordance harness that compares vep-rs output against Perl VEP
(release 115) tuple by tuple and classifies every divergence, and
structural-variant validation VCFs for both assemblies under
tests/sv_validation/. - Documentation under
docs/covering the CLI, output formats, plugins and cache setup, the runbook for reproducing the published concordance underscripts/, and the data and figure sources for the accompanying manuscript undermanuscript/. scripts/check_advisory_reachability.sh, which fails when a known third-party advisory is reachable from thevepbinary rather than merely present somewhere in the workspace, with an expiring allowlist for accepted risks.- Continuous integration on GitHub Actions: tests, Clippy, formatting, a build on the declared minimum supported Rust version, a module-documentation check, a Developer Certificate of Origin check on pull requests, and a release workflow that publishes Linux (x86_64, aarch64) and macOS (aarch64) tarballs with sha256 and md5 checksum files.