fix(snapshot): refuse markers whose format identity the exporter would refuse - #486
Merged
kstonekuan merged 3 commits intoSep 10, 2026
Merged
Conversation
The exporter's replace guard and the new verifier gate asked the same two-part question in two places. If the verifier were ever the looser of the two, exit 0 would start meaning 'some directory with an integrity-shaped key matched'. Shared predicate, separate messages: each caller says what it was trying to do.
Two mutations went unnoticed. Dropping the format-name comparison left the suite green, because the foreign-marker test uses a marker carrying neither field and the version check alone refuses it. Disabling the exporter's replace guard also left it green: that guard had no test anywhere, which matters more now the predicate is shared, since it could be loosened from one side and noticed by neither.
kstonekuan
approved these changes
Sep 10, 2026
Contributor
There was a problem hiding this comment.
LGTM, merging.
Pushed two commits. The gate repeated the exporter's replace guard verbatim, so both now share _marker_identifies_dataset_snapshot with their own messages. Then the tests that makes safe: dropping the format-name comparison and disabling the exporter guard both passed before, the second because that guard had no test anywhere.
4 tasks
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.
fix(snapshot): refuse markers whose format identity the exporter would refuse
Closes #472.
What changed
verify_dataset_snapshotnow pins the marker's format identity at the boundary, immediately after the marker is parsed and before any receipt logic runs. The comparison is deliberately identical to the exporter's replace guard (snapshot.py:618-625) and the writer (:772-773):formatmust equalhflow-dataset-snapshotandformat_versionmust equal"1", so there is exactly one answer to "is this an HFlow snapshot".A marker without that identity, or with a future version, raises through the same
ValueErrorthe unparsable-marker path uses, so the CLI maps it to exit 2 with no new exit code. A foreign directory that happens to carry an integrity-shaped key can no longer be certified; on this base the repro from the issue returnedok: Truefor exactly that input.The maintainer's two notes, applied
"format_version": 1as a JSON int, and the writer records the string"1". The comparison inherits the export guard's strictness, so the int is refused, and per the request the error message says exactly why: it names the found values, the expected values, and states that the version's type is checked because the writer records it as a string.1is the only version there has ever been; refusing anything else at the same boundary sets the version-2 precedent (a future verifier decides what a newer marker can partly read, and that choice gets made then, deliberately).Pre-#401 probe result
The very first exporter (402688e, #132) already wrote both fields unconditionally (writer lines :645-646 of that revision), and every revision since has kept them. So no marker the exporter has ever produced lacks the identity, no valid historical input changes answer, and the pre-#401 no-integrity marker (identity present,
integrityabsent) still returns theno-receiptUNVERIFIABLE path, not a raise.Boundary family and sequencing
These three sit on one function's input boundary, and the maintainer asked for one change rather than three where possible:
content_idgate, built to the maintainer's spec on the issue; this branch is rebased onto it and both gates pass the same snapshots.Gates
15 passed in tests/test_snapshot_verify.py (13 existing + the foreign-marker test + the version test covering both the future-version and int-1 refusals), 31 passed across the verify and dataset-snapshot suites, ruff check / ruff format / ty clean. Mutation run: with the identity gate disabled, exactly the two new raise tests go red while the clean, pre-#401, and moved-root tests stay green; restored, all green. Zero non-ASCII and zero em dashes in changed files.