Skip to content

fix(snapshot): enforce the receipt inventory content_id at verify time - #483

Merged
kstonekuan merged 2 commits into
Hebbian-Robotics:mainfrom
Sagar-024:fix/473-inventory-content-id
Sep 10, 2026
Merged

fix(snapshot): enforce the receipt inventory content_id at verify time#483
kstonekuan merged 2 commits into
Hebbian-Robotics:mainfrom
Sagar-024:fix/473-inventory-content-id

Conversation

@Sagar-024

Copy link
Copy Markdown
Contributor

fix(snapshot): enforce the receipt inventory content_id at verify time

Closes #473.

What changed

verify_dataset_snapshot now enforces the integrity.content_id guarantee documented at docs/how-to/export-dataset-snapshot.md:77. After the receipt is parsed and before the per-file loop, the verifier recomputes the inventory hash from the receipt's own table and asset entries (the identical normalization the exporter uses at snapshot.py:194, via the shared _inventory_content_id helper) and compares it against the stored value.

The exit-2 decision

A content_id that disagrees with the recomputed inventory means the marker is internally inconsistent, which points at tampering or a truncated write, not at damaged bytes. Damaged bytes are what findings and exit 1 are for; an inconsistent receipt is unreadable input. The gate therefore raises through the same ValueError the unparsable-marker path uses (snapshot.py:862), so the CLI maps it to exit 2 with no new exit code. A DAMAGED finding was considered and rejected: findings are evidence about delivered files, and the failure here is in the receipt itself, which per-file findings cannot express without inventing a new reason code for a non-file.

The same handling covers a missing or malformed content_id on a marker that carries an integrity block: one gate, one behavior, one exit code. Pre-#401 markers with no integrity key keep the existing no-receipt UNVERIFIABLE path untouched.

Test shape, and why

  • Pinning test: a receipt entry is removed from the marker and its file is deleted, then verify must raise (and the CLI must map to exit 2). This is the case only the stored content_id catches: the surviving entries agree with each other, so every per-file check passes; the comparison against the hash of the original set is the only witness.
  • Negative control (as asked on the issue): the file is deleted but the receipt entry is kept. This exercises the ordinary missing path and passes without the fix; under the mutation run it stayed green while the pinning test went red, proving the two tests pin different behaviors.

Related issues, separate PRs

Gates

15 passed in tests/test_snapshot_verify.py (13 existing + pinning test + negative control), 31 passed across the verify and dataset-snapshot suites, ruff check / ruff format / ty clean, zero non-ASCII and zero em dashes in changed files. Mutation run: inventory comparison neutered, pinning test red, negative control green, restored, all green.

…cs#473 gate

The gate is two refusals. The mismatch branch is held by the pinning test;
deleting the missing-or-malformed branch left the whole suite green. Four
shapes: absent, empty, non-string, wrong type, each through the API and the
CLI exit code.

@kstonekuan kstonekuan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Merging.

The exit-2 argument is right, and the reason it is right is narrower than the PR body says: integrity and content_id arrived in the same commit (#401), so no snapshot hflow has ever written carries one without the other. A marker with an integrity block and no content_id did not come from us, which is what makes it unreadable input rather than damage.

I verified the two halves separately, because both raise a message containing "content_id" and your match= cannot tell them apart:

mismatch branch removed, missing-id kept       FAILED test_removed_receipt_entry_and_file_raise_inventory_mismatch
missing/malformed content_id branch removed    31 passed  (nothing noticed)
inventory no longer sorted by path             15 failed
exporter stops writing content_id              15 failed

The missing-id branch had no case, so I pushed one: four shapes (absent, empty, non-string, wrong type) through both the API and the CLI exit code.

The last two rows are the nice part. The gate makes the whole verify suite sensitive to the exporter's side of the receipt, so the normalization at :153 and the write at :194 are now pinned by fifteen tests that previously said nothing about them. Neither had a direct test before.

Your negative control is the thing more PRs should carry. Deleting the file while keeping its entry passes with and without the gate, which is what proves the pinning test is testing the gate and not just the deletion.

Validated on the rebased result: ruff, ruff format, ty, 1729 passed / 6 skipped.

#472 next, if you want it. The good first issue pool is best left for newcomers, and you are well past that, so the other thing worth your time is running HFlow against a real corpus (Egocentric-10K or Egocentric-100K on Hugging Face) and reporting what breaks or drags. That feedback is the kind we cannot generate here.

@kstonekuan
kstonekuan merged commit 1890dfb into Hebbian-Robotics:main Sep 10, 2026
6 checks passed
@Sagar-024

Copy link
Copy Markdown
Contributor Author

Thanks @kstonekuan .

#472 is next then i will see what i can do on (Egocentric-10K or Egocentric-100K on Hugging Face)

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.

verify snapshot never checks integrity.content_id, so the documented deleted-member guarantee is unenforced

2 participants