verify-pin-bump
Why this skill exists
CLAUDE.md product invariant:
Every model download the engine performs itself goes through
download_verifiedand its pinned SHA-256. Tests cover that function, not new callers that bypass it.
The pin makes KESHA_MODEL_MIRROR safe (a compromised mirror can't silently swap weights) and turns an upstream HuggingFace republish into a deliberate decision.
Until #174, ASR and lang-id downloads skipped verification. Bumping the pin without confirming the new weights are intentional re-opens that gap.
Hard NO
- ❌ Do NOT comment out
download_verifiedto "make it work". - ❌ Do NOT silently update the SHA without checking what changed upstream.
- ❌ Do NOT use
KESHA_MODEL_MIRROR=to bypass — the mirror produces the same hash check.
Procedure
Step 1: Identify which pin failed
The error message names the file. Find its ModelFile entry in rust/src/models/manifest.rs:
grep -n "sha256:" rust/src/models/manifest.rs | head -40
# locate the entry whose URL or rel_path matches the failing download
Step 2: Re-download cleanly
Wipe the existing cache for that file (it may be a partial / corrupted download), then re-download:
rm -f ~/.cache/kesha/models/<subdir>/<file>
KESHA_CACHE_DIR=/tmp/pin-bump ./rust/target/release/kesha-engine install --tts en ru # bare --tts installs English only
# or, for a single file, curl directly from the URL in models/manifest.rs:
curl -fsSL "<url-from-models-manifest-rs>" -o /tmp/pin-bump-file
Step 3: Compute the actual hash
shasum -a 256 ~/.cache/kesha/models/<subdir>/<file>
# or
shasum -a 256 /tmp/pin-bump-file
Compare to:
- The pin in
rust/src/models/manifest.rs - The HuggingFace UI's reported SHA (open the file URL, click "Copy SHA")
Step 4: Decide intentional vs incident
Three possible scenarios:
A. Upstream legitimately re-published. Verify by checking the HF repo's commit history:
# the URL in models/manifest.rs already points at /resolve/main/<file> — get the latest commit:
curl -sIL "<url>" | grep -i 'x-repo-commit:'
# then inspect the commit:
gh api "repos/<owner>/<repo>/commits/<sha>" --jq '.commit.message'
If the commit message says "fix tokenizer" / "re-export with new ONNX opset" / etc — it's a deliberate upstream update. Proceed to Step 5.
B. Mirror is stale / our HF mirror diverged. If we maintain the mirror (e.g. drakulavich/vosk-tts-ru-0.9-multi), the pin is the source of truth. Re-mirror from upstream and verify SHAs match what we pinned, OR bump the pin if we're moving to a new model version.
C. Compromised mirror / supply-chain attack. If the new hash doesn't match either the upstream OR our mirror's expected content — STOP. Do NOT update the pin. Investigate. This is the threat model the pin protects against.
Step 5: Update the pin (only after Step 4 confirms intentional)
Edit rust/src/models/manifest.rs:
ModelFile {
rel_path: "models/<subdir>/<file>",
url: "https://huggingface.co/.../resolve/main/<file>",
sha256: "<NEW HASH>", // bumped <reason> per <upstream commit / mirror version>
},
If the bump represents a model version change (not just a re-export), also:
- Update the URL/rel_path if needed (e.g.
<old-version>-multi→<new-version>-multi) - Bump any related model dir constant
- Update
docs/tts.mdinstall size table if size changed materially - Diarize (
SortformerNvidiaLow_v*.mlpackage) only: expect a one-time ~98 s cold ANE recompile at the nextkesha install --diarize. Apple's e5rt cache is keyed by the compiled bundle's identity, not its path, so the new model version is a guaranteed cache MISS — even a same-path recompile pays full cost (#444). The warm-at-install step (#437) absorbs this, surfaced by the "one-time compile ~1-2 min on first install" message; it is install-time cost, not atranscribe --speakersfailure. Call it out in the release notes so end users expect the slowinstall --diarizeafter the bump.
Step 6: Verify shape invariants
cd rust && cargo test models::manifest
This catches: hash not 64 hex chars, URL malformed, rel_path duplicate, manifest count off.
Step 7: Run a clean install end-to-end
rm -rf /tmp/pin-bump-cache
KESHA_CACHE_DIR=/tmp/pin-bump-cache ./rust/target/release/kesha-engine install --tts en ru
Expected: completes with no expected sha256 errors.
Step 8: Smoke that the new artifact still works
For TTS bumps:
KESHA_CACHE_DIR=/tmp/pin-bump-cache ./rust/target/release/kesha-engine say --voice <voice-id> "Привет, мир." > /tmp/pin-bump.wav
afplay /tmp/pin-bump.wav # listen — does it sound right?
For ASR bumps:
KESHA_CACHE_DIR=/tmp/pin-bump-cache ./rust/target/release/kesha-engine transcribe <fixture.ogg>
# compare to the previous transcript — material drift means quality may have changed
Step 9: Commit
git add rust/src/models/manifest.rs
git commit -m "chore(models): bump <model> SHA pin for <reason>
Upstream commit: <hf-commit-sha>
Old: <old-hash>
New: <new-hash>
Verified intentional re-export; smoke test passes for <voice/lang>.
"
Step 10 (if release-relevant): release
If this bump should ship, invoke /release vX.Y.Z.
Report
Name the file bumped, the old and new hashes, the evidence that the change is intentional, the smoke result, and the commit.
Anti-patterns to refuse
If asked to bypass verification, refuse and explain:
The SHA pin is what makes
KESHA_MODEL_MIRRORsafe and what makes silent upstream swaps impossible. Removing or bypassing verification re-introduces the regression #174 fixed. Run the verify-pin-bump procedure instead — if the new hash represents an intentional upstream change, the pin will be updated deliberately (one commit, with the upstream commit referenced) rather than silently.
