fix: attest immutable runtime model versions

This commit is contained in:
velixio
2026-08-15 18:26:42 +05:30
parent 37c8be6bfe
commit df2da4bb4d
3 changed files with 30 additions and 5 deletions
+4 -3
View File
@@ -69,9 +69,10 @@ The Go preflight (`internal/gateway/preflight.go`) fails closed unless:
- `catalog_model_id` — the VoiceStudio TTS engine id (`omnivoice`,
`voxcpm2`, …) from `services.tts_backend`'s registry.
- `model_version` — the installed Hugging Face revision (40-char commit SHA)
recorded by `services.hf_revisions` (curated pin or the
`voicestudio-revision` marker).
- `model_version` — an immutable catalog version comprising the installed
Hugging Face revision (40-char commit SHA) and the first 16 hex characters
of the attested snapshot digest. This creates a new catalog identity when
snapshot bytes change; it never rewrites an identity retained by a Job.
- `model_digest` — `sha256:<hex>` computed over the installed snapshot files
(sorted relative path + per-file SHA-256), cached next to the repo cache
keyed by (revision, file list, sizes, mtimes) so multi-GB weights are
+15 -1
View File
@@ -63,6 +63,20 @@ ENGINE_MODEL_REPOS: dict[str, str] = {
}
def catalog_model_version(revision: str, model_digest: str) -> str:
"""Return the immutable catalog version for an attested model snapshot.
A Hugging Face revision names source history, not necessarily the exact
snapshot bytes installed on a node. The catalog version therefore carries
a short, deterministic digest suffix. A changed snapshot becomes a new
catalog identity instead of mutating an identity retained by Jobs.
"""
digest = model_digest.removeprefix("sha256:")
if len(revision) != 40 or len(digest) != 64:
raise ValueError("model identity requires a SHA revision and SHA-256 digest")
return f"{revision}+sha256-{digest[:16]}"
def slots_per_device(default: int = 1) -> int:
raw = os.environ.get(SLOTS_ENV, "").strip()
try:
@@ -233,7 +247,7 @@ class ProductionInventory:
return _replace_state(base, STATE_LOADING)
return ModelInfo(
catalog_model_id=base.catalog_model_id,
model_version=base.model_version,
model_version=catalog_model_version(base.model_version, model_digest),
model_digest=model_digest,
precisions=base.precisions,
features=base.features,
@@ -27,6 +27,7 @@ from runtime_adapter.inventory import (
STATE_INSTALLED,
STATE_LOADING,
ModelInfo,
catalog_model_version,
)
from runtime_adapter.selfcheck import PreflightError, run_preflight
from runtime_adapter.server import prepare_socket
@@ -76,7 +77,7 @@ def test_capabilities_report_device_and_ready_model_evidence(tmp_path):
by_id = {model.catalog_model_id: model for model in caps.models}
ready = by_id[READY_MODEL.catalog_model_id]
assert ready.state == pb2.RUNTIME_MODEL_STATE_READY
assert len(ready.model_version) == 40
assert ready.model_version.startswith("d" * 40 + "+sha256-")
assert ready.model_digest.startswith("sha256:")
assert list(ready.precisions)
# A loading/failed/installed model is reported truthfully, never READY.
@@ -144,6 +145,15 @@ def test_snapshot_digest_is_stable_and_content_sensitive(tmp_path):
snapshot_digest(tmp_path / "empty-none")
def test_catalog_model_version_changes_when_attested_snapshot_changes():
revision = "d" * 40
first = catalog_model_version(revision, "sha256:" + "a" * 64)
second = catalog_model_version(revision, "sha256:" + "b" * 64)
assert first.startswith(revision + "+sha256-")
assert first != second
def test_file_sha256_matches_hashlib(tmp_path):
import hashlib