fix(svar2): scope the link fingerprint to payload files (#419) - #421
Merged
Merged
Conversation
`_svar2_store_fingerprint` walked the whole `.svar2` store with
`rglob("*")`, so any file added beside the payload changed the recorded
`{n_files, store_bytes}` and every dataset linked to that store began
failing verification. The docstring already promised the narrow
contract; the implementation did not honour it.
Scope the fingerprint to the payload: each contig's `dense`, `fields`,
`indel` and `var_key` subtrees plus `max_del.npy`, counting only
`.bin`/`.npy`. Writing an annotation layer (`mutcat/`, say) alongside
the payload no longer invalidates the link.
Narrowing the scope alone would break datasets that verify cleanly
today -- a link recorded under the old broad scope against a store that
already had sibling layers would newly mismatch. So a fingerprint that
matches the legacy broad walk is still accepted, as a second exact
match. Only a value matching neither is a mismatch.
On mismatch the error now says which scope it used, what the payload
actually measures, and whether a sibling layer can account for the
difference -- including the case where the recorded value is larger
than the payload and no layer remains, which is what both a
pre-scoping record and real truncation look like. The message says so
rather than guessing.
All new symbols are private; no public API change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #419.
The bug
_svar2_store_fingerprintwalked the whole.svar2store withrglob("*"), so any file written beside the payload changed the recorded{n_files, store_bytes}, and every dataset linked to that store started failing verification with a message that reads like corruption. The docstring already promised the narrow contract; the implementation did not honour it.Hit for real: an aster corpus recorded its link while a
mutcat/annotation layer was present, and every consumer of that store now refuses to open.The fix, in three parts
1. Scope the fingerprint to the payload. Each contig's
dense,fields,indel,var_keysubtrees plusmax_del.npy, counting only.bin/.npy. Adding or removing a sibling annotation layer no longer touches the fingerprint.2. Don't break the datasets that verify today. Narrowing the scope alone is a backward-compatibility regression: a link recorded under the old broad walk, against a store that already carried sibling layers, would newly mismatch. So
_svar2_legacy_store_fingerprintrecomputes the old broad value and is accepted as a second exact match. Only a value matching neither scope is a mismatch. This is covered bytest_a_link_recorded_before_scoping_still_verifies, andtest_legacy_acceptance_does_not_excuse_a_modified_payloadpins that the escape hatch stays exact — it does not become a blanket amnesty.3. Make the mismatch message actionable. It now states the scope it used, what the payload actually measures, and whether a sibling layer can account for the difference. The one genuinely ambiguous case — recorded value larger than the payload, no layer left to explain it — is what both a pre-scoping record and real truncation look like, from two integers with no stored baseline manifest. The message says so and names the discriminating check rather than guessing:
Tests
tests/unit/dataset/test_svar2_link.pygrows 4 → 11. New helper_add_annotation_layerwrites a realistic sibling layer. New cases cover: additive layer, layer removal, a genuinely new payload file still being caught, a real payload mismatch ruling out the sibling-layer explanation, an oversized record not being called corruption, legacy acceptance, and legacy acceptance not excusing a modified payload.Verification
ruff format python/ tests/— 270 files unchangedruff check python/ tests/— cleanpixi run -e dev typecheck— 0 errorspixi run -e dev pytest tests -q— 1323 passed, 56 skipped, 4 xfailedTwo failures in
tests/unit/test_threads.py(assert 16 == 52) are environmental — this pod reports 16 CPUs by affinity vs 52 by cgroup — and were confirmed failing on cleanmainviagit stashbefore this branch existed.API surface
No public API change: every new symbol is
_-prefixed and private, soskills/genvarloader/SKILL.mdneeds no update.🤖 Generated with Claude Code