Skip to content

Tell the validation oracle how the graph was actually built - #14

Merged
ecrum19 merged 3 commits into
mainfrom
fix/validation-representation-passthrough
Sep 15, 2026
Merged

ecrum19 merged 3 commits into
mainfrom
fix/validation-representation-passthrough

Conversation

@ecrum19

@ecrum19 ecrum19 commented Sep 15, 2026 •

Copy link
Copy Markdown
Owner

The bug

A conversion run with --info-representation raw could not pass validation. The oracle reported the entire structured layer missing — the allele layer, value items, InfoFieldValue/InfoFieldDefinition, and all 256 buckets of the INFO value digest — and the run exited 1.

Root cause

The oracle was right, and already handled this. expected_census only adds that layer when info_representation == "structured", and the code even says so:

# The allele layer, the value items, the SV carriers and the parsed
# genotype layer travel with the structured INFO representation.

It just never learned which mode produced the graph. The single call site was:

parser = attach_census_expectations(parser, args.representation)

info_representation and header_representation are keyword-only with default "structured", so every run was scored against a structured oracle. The validation runner's CLI had no flag for either, so the wrapper had no way to say otherwise across the container boundary — run_validation_mode did not even accept the values, though run_full_mode had them all along.

A second, independent gate was missing: q12_info_value_digest is computed in parse_vcf, where the emitter's representation is not in scope, so it survived unconditionally even once the flags were threaded.

Changes

  • validation_runner.py gains --info-representation and --header-representation, and passes them at the call site.
  • run_validation_mode takes both as required keyword arguments and forwards them. Full mode had them and never passed them on; validate mode reads them from args like sample_representation does.
  • expected_census and attach_census_expectations lose their defaults. The default was the bug: it let a caller omit the parameter and silently get wrong expectations instead of a TypeError.
  • q12_info_value_digest is gated in attach_census_expectations, beside the existing q13 gate on sample representation.

Why no test caught it

The oracle's raw/structured branch was already exercised — validation_fixtures passes both values explicitly, and parser_summary(include_info=False) already yields an empty digest. So the logic was covered while the wiring between wrapper and runner was not, and the fixture and the production oracle quietly disagreed about q12.

Added tests for the wiring itself: that the flags reach the runner's argv, that the runner's CLI accepts what the wrapper sends, that the census helpers refuse to guess, and that raw INFO expects no value digest.

Verification

Unit suites: 378 tests across test_validation_{engines,oracle,logic,results,benchmark}_unit and test_vcf_rdfizer_unit, all passing.

End to end on a benchmark VM with an image built from this branch, running the full 11_covering_set sweep (test-1k.vcf, comunica):

row INFO storage reprs strategy result
row1 structured space-optimized hdt,cottas partitioned exit 0, 164s
row2 raw plain hdt single exit 0, 92s
row3 raw plain hdt auto exit 0, 92s
row4 structured space-optimized cottas auto exit 0, 114s
row5 structured space-optimized hdt,cottas partitioned exit 0, 162s
row6 raw plain cottas partitioned exit 0, 96s

Suite exit 0, 6/6 rows recorded. Before the fix, row2 failed and aborted the sweep, so it recorded 2 of 6 rows — rows 3–6 had never run at all.

Also in this PR: pinning pip in CI

Separate from the validation fix, and included here because it surfaced while
verifying this branch.

Every CI job began with pip install --upgrade pip, so each run resolved
dependencies with whatever pip released most recently. That makes CI
non-reproducible by construction — the same commit can pass or fail depending on
the day's pip, with nothing in the repository to show for it. PIP_VERSION is
now pinned once per workflow (26.2.1) and referenced from all six install
steps, so a resolver change arrives as its own reviewable commit.

This does not fix a transient index failure, and is not offered as one. The
failure that prompted it looked like this:

ERROR: Cannot install rich because these package versions have conflicting dependencies.
    markdown-it-py 4.2.0 depends on mdurl~=0.1
Additionally, some packages in these conflicts have no matching distributions
available for your environment:
    mdurl

The evidence that it was the index rather than this branch:

run trigger result
09:08 push (d5a2087) pass
09:13 push (3f93b47) pass
09:28 pull_request, same code fail on ubuntu-latest only
re-run no code change pass on all three platforms

ubuntu-22.04 and macos-latest passed inside the same failing run, and mdurl
is a pure-Python py3-none-any wheel while the job pins Python 3.11 — so nothing
about the environment made it uninstallable. The right response to that is a
re-run; the right response to an unpinned toolchain is the pin.

build, twine and coverage remain unpinned in the publish and coverage jobs.
The same argument applies to them, and they are deliberately left for a separate
change.

How it was found

11_covering_set on a live benchmark run. Row 2 is the only row pairing raw INFO with single-strategy HDT. Worth noting the blast radius: only 07_representation_axes and 11_covering_set vary info-representation, and 07 does not validate — so this is invisible unless the covering set runs.

🤖 Generated with Claude Code

ecrum19 and others added 2 commits September 15, 2026 11:08
A conversion run with --info-representation raw could not pass validation. The
oracle reported the entire structured layer missing -- the allele layer,
value items, InfoFieldValue/InfoFieldDefinition, and all 256 buckets of the
INFO value digest -- and the run exited 1.

The oracle was right and already handled this: expected_census only adds that
layer when info_representation == "structured". It just never learned which
mode produced the graph. attach_census_expectations was called as

    attach_census_expectations(parser, args.representation)

and both info_representation and header_representation are keyword-only with
default "structured", so every run was scored against a structured oracle. The
validation runner's CLI had no flag for either, so the wrapper had no way to
say otherwise across the container boundary.

- validation_runner.py gains --info-representation and --header-representation
  and passes them at the call site.
- run_validation_mode takes both as required keyword arguments and forwards
  them to the runner. Full mode already had them and simply never passed them
  on; validate mode reads them from args like sample_representation does.
- expected_census and attach_census_expectations lose their defaults. The
  default was the bug: it let a caller omit the parameter and silently get
  wrong expectations instead of a TypeError.

The oracle's raw/structured branch was already exercised -- validation_fixtures
passes both values explicitly -- so the logic was covered while the wiring
between wrapper and runner was not. Added tests for the wiring itself: that the
flags reach the runner's argv, that the runner's CLI accepts what the wrapper
sends, and that the census helpers refuse to guess.

Found by 11_covering_set, whose row 2 is the only row pairing raw INFO with
single-strategy HDT. That row failing also aborted rows 3-6, so the sweep
recorded 2 of 6 rows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
With the representation threaded through, q09_predicate_census and
q10_class_census came back clean but q12_info_value_digest still reported all
256 buckets missing against zero extra rows -- the graph produced nothing and
the oracle expected a full digest.

q12 hashes the decomposed INFO value items, which only the structured
representation emits; under raw INFO the column stays one opaque literal and
the query matches nothing. But the digest is computed from the VCF in
parse_vcf, where the emitter's representation is not in scope, so it survived
unconditionally. Gate it in attach_census_expectations, next to the existing
q13 gate on sample representation.

validation_fixtures already encodes this -- parser_summary(include_info=False)
yields an empty digest -- so the fixture and the production oracle disagreed.
The new test pins them together.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Every job began with `pip install --upgrade pip`, so each run resolved
dependencies with a different, unannounced resolver. CI was non-reproducible by
construction: the same commit could pass or fail depending on what pip shipped
that morning, with nothing in this repository to show for it.

Pin it once per workflow as PIP_VERSION and reference that from all six install
steps, so a resolver change lands as its own reviewable commit.

What this does not do is prevent transient index failures, and it should not be
read as a fix for one. The case that prompted it: the pull_request run for
PR #14 failed with

    no matching distributions available for your environment: mdurl

while the identical commit had passed 15 minutes earlier on push, passed on
ubuntu-22.04 and macos-latest in that same run, and passed again on re-run with
no code change. mdurl is a pure-Python py3-none-any wheel and the job pins
Python 3.11, so nothing about the environment made it uninstallable -- that was
the index, not the resolver, and the answer to it is a re-run. Pinning was worth
doing on its own merits, not because it would have prevented that failure.

Note that build, twine and coverage are still unpinned in the publish and
coverage jobs. Same argument applies to them; left alone here so this commit
stays about the toolchain that resolves everything else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ecrum19
ecrum19 merged commit d44b3de into main Sep 15, 2026
24 checks passed
ecrum19 added a commit that referenced this pull request Sep 22, 2026
…items

Brings in #14-#17. The overlap that matters is CottasEngine: dev still had the
pycottas subprocess form, main replaced it with the comunica-backed endpoint.
Git merges the two cleanly because they touch different regions, and the
comunica implementation wins -- verified here that no COTTASStore or
COTTAS_QUERY_RUNNER remnant survives, so the merged engine is coherent rather
than merely conflict-free.
@ecrum19
ecrum19 deleted the fix/validation-representation-passthrough branch September 22, 2026 14:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants