You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#401 landed per-file integrity receipts in a snapshot's format.json (#397). Nothing consumes them. The receipt is the half of the feature that survives a transfer; the half that checks it does not exist yet, and the how-to says so in as many words: "there is no public verify_dataset_snapshot API or CLI yet".
Read nowhere. The only reader of format.json is the overwrite guard at :595-622, and it looks at format and format_version only. So today the failure this feature exists to catch, a snapshot that arrived truncated or short a table, is silent: DuckDB opens what is there and returns fewer rows.
What to build
A verify_dataset_snapshot(snapshot_directory) beside export_dataset_snapshot, exported from hflow/__init__.py, plus an hflow verify snapshot CLI command. It re-reads the delivered directory and compares it against the receipt.
The helpers are already there and are the right shape to reuse: _sha256_hex at :116, _file_integrity_record at :125, _inventory_content_id at :134.
Four distinct failures worth separating, because a caller does different things about each:
A file's bytes changed.sha256 mismatch on a table or an asset.
A file is missing. Named in the receipt, absent on disk.
A file arrived that the receipt does not name. Extra members under assets/.
size_bytes is not a separate failure. It is a cheap pre-filter: compare it first and skip the hash when it already differs, which matters on a copy-mode snapshot holding video.
What it is not
Not a tamper defence, and the API should not imply one. The receipt travels unsigned inside the format.json it describes, so anyone who can rewrite a table can rewrite the hashes to match. This catches corruption, truncation, and partial transfer. If the naming or the docstring makes it sound like a signature, that is a bug in this issue's output.
Design questions worth answering in the PR
Return or raise.export_dataset_snapshot returns a DatasetSnapshotReport. A verifier that raises on the first mismatch is less useful than one that returns every mismatch it found, because a partial transfer usually damages more than one file and the caller wants the list. A report object with a boolean and a list of typed findings fits the repo better than an exception, and gives the CLI something to print. Argue for whichever you pick.
Exit code. The CLI convention here is 2 for bad input. A snapshot that fails verification is not bad input, it is a true answer to the question asked. Pick a code, say why.
Cost. Verification is a full re-read of every delivered byte. On a copy-mode snapshot that is the media again. Consider whether a --quick mode that checks sizes and the member set without hashing is worth having, and whether its result is honest enough to report as "verified".
Definition of done
The four failure classes above are each covered by a test that fails for that reason alone.
A clean snapshot verifies clean, in both references and copy mode.
#401 landed per-file integrity receipts in a snapshot's
format.json(#397). Nothing consumes them. The receipt is the half of the feature that survives a transfer; the half that checks it does not exist yet, and the how-to says so in as many words: "there is no publicverify_dataset_snapshotAPI or CLI yet".Current state
Written at
src/hflow/snapshot.py:187:Read nowhere. The only reader of
format.jsonis the overwrite guard at:595-622, and it looks atformatandformat_versiononly. So today the failure this feature exists to catch, a snapshot that arrived truncated or short a table, is silent: DuckDB opens what is there and returns fewer rows.What to build
A
verify_dataset_snapshot(snapshot_directory)besideexport_dataset_snapshot, exported fromhflow/__init__.py, plus anhflow verify snapshotCLI command. It re-reads the delivered directory and compares it against the receipt.The helpers are already there and are the right shape to reuse:
_sha256_hexat:116,_file_integrity_recordat:125,_inventory_content_idat:134.Four distinct failures worth separating, because a caller does different things about each:
sha256mismatch on a table or an asset.assets/.format.jsonfrom before feat(snapshot): record table and copied-asset integrity in format.json (#397) #401. This is a valid v1 snapshot and must not be reported as corrupt. Say the receipt is absent and stop.size_bytesis not a separate failure. It is a cheap pre-filter: compare it first and skip the hash when it already differs, which matters on a copy-mode snapshot holding video.What it is not
Not a tamper defence, and the API should not imply one. The receipt travels unsigned inside the
format.jsonit describes, so anyone who can rewrite a table can rewrite the hashes to match. This catches corruption, truncation, and partial transfer. If the naming or the docstring makes it sound like a signature, that is a bug in this issue's output.Design questions worth answering in the PR
export_dataset_snapshotreturns aDatasetSnapshotReport. A verifier that raises on the first mismatch is less useful than one that returns every mismatch it found, because a partial transfer usually damages more than one file and the caller wants the list. A report object with a boolean and a list of typed findings fits the repo better than an exception, and gives the CLI something to print. Argue for whichever you pick.--quickmode that checks sizes and the member set without hashing is worth having, and whether its result is honest enough to report as "verified".Definition of done
referencesandcopymode.format.jsonwith nointegritykey is reported as unverifiable, not as corrupt, and does not raise.__all__.Validation
Markdown changes also want the lychee command from CONTRIBUTING.md.