feat(snapshot): record table and copied-asset integrity in format.json (#397) - #401
feat(snapshot): record table and copied-asset integrity in format.json (#397)#401VARUN3WARE wants to merge 2 commits into
Conversation
Hebbian-Robotics#397) Keep format version 1 and add path/size_bytes/sha256 receipts for required Parquet tables and copy-mode assets, plus a content_id over the normalized inventory so missing members are detectable without shipping a verifier yet.
kstonekuan
left a comment
There was a problem hiding this comment.
Holding this one, and the reason is a shape change rather than anything wrong with the work. The receipt content is right: per-file sha256, the inventory content_id that makes a deleted member visible, and references mode leaving assets empty rather than fetching remote media to hash it. Gate is clean here too, 16 passed.
tables changed type under an unchanged format version. It was a name-to-filename mapping and is now a name-to-record mapping:
- "tables": {
- "samples": _SAMPLES_TABLE_FILE_NAME, # "samples.parquet", a string
+ tables[table_name] = _file_integrity_record(file_name, absolute_path) # a dictSo format_marker["tables"]["samples"] returns "samples.parquet" before this and {"path": ..., "size_bytes": ..., "sha256": ...} after, with format_version still 1. Main's own documentation calls that section the Format contract and says the marker "names each table", so the mapping is published, not incidental. Nothing in the repo reads it besides the tests you updated, but external readers are the entire audience for a snapshot: #397's premise is that this artifact moves independently of the catalog.
This one is partly on me. My direction said added keys are backward compatible and that adding under v1 beats a version break. That was true of adding, and I did not say the obvious corollary: not redefining a field that is already there. Reading it back, it invited exactly this.
The fix, and the shape is yours to pick. Put the integrity block under its own key and leave tables alone:
"tables": { "samples": "samples.parquet", ... },
"integrity": { "tables": { "samples": { "path": ..., "size_bytes": ..., "sha256": ... } },
"assets": [ ... ],
"content_id": "..." }That keeps the whole thing genuinely additive, which makes your doc sentence about backward compatibility true rather than nearly true, and it keeps the verifier purely a reader. Nesting the per-table receipts under integrity.tables rather than flattening them also leaves room for a future non-table member without another reshuffle.
Two smaller things while you are in there.
_build_snapshot_integrity_marker_fields hashes every copied asset, so copy mode now reads every media byte a second time on export. For a snapshot with real video that is a visible slowdown and nobody has costed it. It is very likely worth paying, but say so in the PR with a rough number rather than leaving it for someone to discover.
The 16-hex truncation on content_id deserves a sentence. Matching #389's width is a reasonable instinct for a familiar shape, but that value identifies an episode while this one is the delivery's integrity digest, and #397 cited Croissant's full-SHA-256 recommendation. The per-file hashes are full length so the exposure is limited to the set digest, and 64 bits is ample against corruption while being weak against deliberate tampering. Either keep it and say which threat it covers, or make it full length; I lean toward full length, since nothing here needs it to be short.
The FileNotFoundError when a required table is missing from staging is fine and I would keep it: staging always writes all seven, so it is an invariant check rather than an error path, and failing loudly there beats publishing a marker that promises a file nobody wrote.
Nothing else needs changing, and none of the test work is wasted; the assertions move to a new path rather than being rewritten.
Leave the published name-to-filename tables map unchanged under format v1, move receipts to integrity.tables/assets, and use a full-length inventory content_id. Documents the copy-mode second media read for hashing.
|
Thanks, reshaped it so this stays additive under format v1:
|
Summary
format_versionat1and add delivery integrity toformat.json: each required Parquet table getspath/size_bytes/sha256.assets/gets the same receipt fields inline (no separate asset manifest); references mode leavesassetsempty and does not fetch remote media.content_idover the normalized inventory so a deleted member is detectable later without a second format change.Closes #397
Test plan
uv run ruff checkuv run ruff format --checkuv run ty checkuv run pytest tests/test_dataset_snapshot.py -q(16 passed)uv run pytest -q(1529 passed, 6 skipped)Notes
content_idwidth matches prepared-manifest episodecontent_idfrom feat(import): per-episode receipts in prepared-manifest.json #389 for a familiar receipt shape.