Skip to content

feat(verify): check LeRobot prepared-manifest deliveries against receipts - #454

Merged
kstonekuan merged 1 commit into
Hebbian-Robotics:mainfrom
VARUN3WARE:feat/verify-lerobot-import
Sep 7, 2026
Merged

kstonekuan merged 1 commit into
Hebbian-Robotics:mainfrom
VARUN3WARE:feat/verify-lerobot-import

Conversation

@VARUN3WARE

@VARUN3WARE VARUN3WARE commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Test plan

@Sagar-024 Sagar-024 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.

Approved. The part I like most is exit_code_for: 0, 1, and 3 live in one function, 2 is raised for unreadable input before a report exists, and FORMAT.md documents the split so CI can tell an unverifiable delivery from a damaged one. That split is easy to get wrong per command.

Checked against what the #432 contract needs: VerificationReport with .ok and .findings, each finding carrying .uri, .reason, and .detail, and the module importing from catalog and storage only, never from snapshot.py or lerobot.py. Extras ignored, nothing rewritten, nothing re-converted. Holds.

One coordination note for the snapshot verifier (#428): it will consume these same types, so its reasons need to live beside REASON_MISSING and friends rather than inventing a second vocabulary, and once two verifiers share this module it may be worth separating the shared types from the per-delivery verifiers. Neither blocks this PR.

@VARUN3WARE

Copy link
Copy Markdown
Contributor Author

Thanks @Sagar-024 , agreed on both notes for #428.

I'll keep snapshot reason strings beside the existing REASON_* constants in verification.py (same vocabulary, no second set of names). Happy to split shared types from the per-delivery verifiers in a follow-up once both verifiers are in-tree; not blocking this PR.

CI is green here; ready for maintainer merge whenever @kstonekuan has a slot.

@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.

The contract is right; this is not about the shape.

The verifier reads the original rather than the copy, so a damaged copy verifies clean. Have a look at which path it opens.

@VARUN3WARE

Copy link
Copy Markdown
Contributor Author

@kstonekuan Fixed: episodes resolve as landing/<basename> under the verified root; receipt uri is provenance only. Empty episodes → ok. Added your three copy-root cases plus a bucket relative-fetch test. Ready for another look.

@Sagar-024 Sagar-024 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.

Re-reviewed the fix commit (25a69e3). Stale approval replaced, this still holds the #432 contract checklist:

  • CLI verb: hflow verify lerobot-import <root> present.
  • Report: .ok derived from status, findings in one list, no raise on first mismatch.
  • Per-finding uri / reason / detail: present, reasons from the shared constants.
  • Shared types: single hflow.verification module, no verifier-local report shapes.
  • Exit codes: 0 clean, 1 damaged, 2 unreadable input (raises), 3 unverifiable, via exit_code_for.
  • Extras ignored: unlisted files under landing/ produce no finding.

The fix itself, verified in a scratch worktree (test_verification.py: 12 passed):

  • Episodes now resolve as landing/<basename> under the root being verified; the receipt uri is provenance, never a lookup path.
  • Both directions are pinned by mutation: mutating only the original keeps the copy green, damaging only the copy goes red.
  • The deleted-original case and the bucket-root case are covered, plus the empty-claim receipt flipping to clean (the right call: nothing claimed, nothing damaged).
  • Publish-time absolute uris still parse after a copy, so no schema bump is needed.

One note, non-blocking: .zcode shows a stray pr454.diff in my scratch from the review run; it is mine, not part of the PR.

Re-approving on the fix commit.

@Sagar-024

Copy link
Copy Markdown
Contributor

@VARUN3WARE Kingston unblocked the order, so I am landing #428 first with your type shape verbatim: VerificationStatus, VerificationFinding, VerificationReport with the ok property, your three REASON constants and exit_code_for, plus one REASON_NO_RECEIPT beside yours, hyphenated to match your convention. When you rebase #454, drop your copy of verification.py and import from the landed one. Your fix stays in the import-verifier half (the landing resolution and the empty-claim case), so nothing is lost. Ping me after the rebase and I will review again.

@Sagar-024

Copy link
Copy Markdown
Contributor

@kstonekuan carried into #428: the moved-root test exports a snapshot to one root, copies it to a second, deletes the first entirely, then verifies the copy clean and then damaged, plus a variant where the original stays alive and clean while the copy is damaged, so damage is never masked by the export root. PR #457 is up with it, and the shared types land in VARUN's exact shape so #454 re-lands as an importer.

@VARUN3WARE

Copy link
Copy Markdown
Contributor Author

@Sagar-024 Thanks for the re-review. Ack on order: once #457 lands I’ll rebase #454, import the shared types from your verification.py, and keep only the lerobot-import resolution / empty-claim / CLI side. Will ping you after the rebase.

@kstonekuan

Copy link
Copy Markdown
Contributor

Fix confirmed with the same probe that caught it. Before, the third line read ok; now:

original verify           -> ok
copy, original present    -> ok       findings=0
copy damaged, original ok  -> damaged  findings=['content-id-mismatch']
copy alone, original gone  -> damaged  findings=['content-id-mismatch']

The third line is the one that mattered: damage in the copy is found even with an intact original sitting next to it, so the verifier is reading what it was handed. The fourth is my probe still holding the damaged copy from the step before, so damaged is the right answer there too.

All four cases from the review are in, and test_verify_lerobot_import_resolves_bucket_deliveries_under_the_verified_root goes past what I asked for. Reasoning the bucket case rather than testing it would have been acceptable and testing it is better.

You also changed episodes: [] from unverifiable to ok, which I agree with. A manifest in a format we understand that claims nothing has been verified against everything it claims. Exit 3 should mean "go and look", and there is nothing to look at.

Not approving yet only because main moved underneath you: #457 merged and landed src/hflow/verification.py, so this now has an add/add conflict there plus one in __init__.py. That is the rebase you and @Sagar-024 planned for, in the order you agreed.

When you do it: take the merged module as the base and add to it rather than reconciling two copies. VerificationStatus, VerificationFinding, VerificationReport, exit_code_for and the three shared reason strings are already there, and REASON_NO_RECEIPT arrived with the snapshot verifier. Your verify_lerobot_import and _landing_relative_key_from_receipt_uri are the new parts. If you find yourself editing the shared types to fit, say what does not fit instead, because that is a contract question rather than a merge.

The basename mapping is worth one line in the PR body when you push: it means the verifier and the importer share the landing layout without the manifest recording it. That is fine, and it is the sort of coupling that should be stated rather than discovered.

Ping me when it is clean and I will merge.

Import VerificationReport from the landed verification module (Hebbian-Robotics#457) and keep
the import-delivery half in lerobot_verify: resolve landing/<basename> under
the verified root, treat empty episodes as ok, and cover Kevin's copy cases.
@VARUN3WARE
VARUN3WARE force-pushed the feat/verify-lerobot-import branch from 25a69e3 to 771aa6a Compare September 7, 2026 15:54
@VARUN3WARE

Copy link
Copy Markdown
Contributor Author

@kstonekuan Rebased onto main after #457. Dropped our duplicate verification.py types and import yours; LeRobot half lives in importers/lerobot_verify.py (root-relative landing/<basename>, empty claim → ok, copy-root tests). Ready for another look when you have a minute.

@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.

Clean rebase. The verifier moved to src/hflow/importers/lerobot_verify.py, the shared types are imported rather than reconciled, and the only edit to verification.py is a docstring now naming both concrete verifiers. That is the right shape: one contract, two implementations, neither owning the other.

Validated on the rebased branch: full gate clean, 1632 passed / 6 skipped. The path fix stays fixed, which I checked with the probe from the first round rather than trusting the rebase.

Merging. Between this and #457 the verify family is real: same report, same reason strings, same exit codes, two receipt formats.

Worth writing down somewhere eventually, though not in this PR: the basename mapping means the verifier and the importer share the landing layout without the manifest recording it. You noted it, and the next person to change _landing_relative_key needs to know a verifier depends on it. If a schema-4 receipt ever happens, carrying the relative key is the thing that removes the coupling.

Good work on both rounds, and on settling the contract with @Sagar-024 in writing before either of you wrote code. That is why these merged in two days instead of arguing in review.

@kstonekuan
kstonekuan merged commit 4a20f1e into Hebbian-Robotics:main Sep 7, 2026
6 checks passed
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.

3 participants