Goal
Every catalog download is a fixed byte sequence. Users must not get a different file because someone pushed to main.
Pin shape
Each catalog row pins three values that must change together:
- HuggingFace commit (40-char lowercase hex) in the resolve URL — never the branch name
main(or any other moving ref). - File SHA-256 (64-char lowercase hex) — checked after download and at load.
- Exact sizeBytes — gates free-space check and post-download length.
URL form:
https://huggingface.co/litert-community/<repo>/resolve/<commit>/<fileName>
Attested
A pin is attested when the HuggingFace tree API for that commit reports:
.lfs.oid(or the file sha) equal to the catalog SHA-256.lfs.size(orsize) equal tosizeBytes
Cross-check: curl -sI -L <downloadUrl> x-linked-etag should echo the same digest.
Must not
- Write
/resolve/main/or any other branch ref into Kotlin or this bundle. - Change commit without digest and size, or digest without commit.
- Auto-bump because
mainmoved. Record the newer commit in the snapshot as available, leave runtime pins alone. - Add or remove a catalog model without an explicit product decision.
- Apply a digest to user-imported files (imports are exempt).
When to bump
Only when product asks to ship a newer revision (quality fix, new weights). Then set commit + SHA-256 + sizeBytes in one change, update this bundle, and keep display name / GPU baseline / context defaults unless those were part of the decision.
When to keep a stale pin
If main is newer than the pin, the pin is still valid as long as the pinned commit still exists and its digest matches. That is the expected state between product bumps.
If the pinned commit is gone from HuggingFace or the digest at that commit no longer matches, keep runtime as-is (do not invent a new pin) and flag it in the log — a broken pin needs a product bump, not a silent rewrite.