feat(models): add pinned backend manifests and offline verification

This commit is contained in:
baketnk committed 2026-09-24 22:16:13 -04:00
1 parent 07c03ea27e
commit 5cbbda3ec2
8 files changed
+589 -53

No files matched your search

+70
View File
@@ -0,0 +1,70 @@
"""Manifest schema tests: adding a second backend requires no C++ worker changes."""
import copy
import json
from pathlib import Path
import sys
import tempfile
import unittest
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "python"))
from frameyap.model_files import DEFAULT_MANIFEST_DIR, ManifestError, load_backends
class BackendTests(unittest.TestCase):
def test_redux_manifest_preserves_pinned_artifacts_and_launcher_contract(self):
redux = load_backends()["redux"]
self.assertEqual(redux.revision, "fad622f25f303105c20d70e201bcc477c88b620c")
self.assertEqual({f.path: (f.size, f.sha256) for f in redux.files}, {
"model.safetensors": (177774490, "78ec25733ee0d0c1586d1346fc86db9d0c2e436e3a8ab1d32a82d1bb8f848d21"),
"config.json": (12988, "503c653b2e3bb788adbcb04f5abdee532d958686564081baeed133ff10143f6e"),
"ternary.json": (57970, "1221c6d3ce901ffe09c089da758a8db8b76189f80cff41c5afc244fc61e2051d"),
"tokenizer.json": (1159960, "bd321b096832a3f270bd3b2a88823957920f1a5c5ada71114a26ea729d0cbe91"),
"README.md": (8533, "a8b327f983a8b8ff262ff7bead3a791fbed9350632002af8db85ab5cd84cdaa5"),
})
self.assertEqual(redux.launcher["protocol"], "frameyap-worker-v1")
self.assertEqual(redux.launcher["arguments"], ["--model", "{model_dir}", "--threads", "{threads}", "--clip-dir", "{clip_dir}"])
self.assertIn("CC-BY-4.0", redux.license_id)
def test_second_backend_and_strict_schema(self):
fixture = json.loads((DEFAULT_MANIFEST_DIR / "redux.json").read_text())
with tempfile.TemporaryDirectory() as path:
root = Path(path)
def save(value, name="other.json"):
(root / name).write_text(json.dumps(value))
fixture["id"] = "other"
fixture["display_name"] = "Independent offline test backend"
fixture["launcher"] = {"type": "executable", "path": "bin/other-worker",
"arguments": ["--model", "{model_dir}", "--clip-dir", "{clip_dir}"],
"protocol": "frameyap-worker-v1"}
fixture["model"]["files"] = [{"path": "nested/model.bin", "size": 3, "sha256": "a" * 64}]
save(fixture)
self.assertEqual(load_backends(root)["other"].files[0].path, "nested/model.bin")
(root / "redux.json").write_bytes((DEFAULT_MANIFEST_DIR / "redux.json").read_bytes())
self.assertEqual(list(load_backends(root)), ["other", "redux"])
for edit in (
lambda f: f.update(schema=2),
lambda f: f.update(unexpected="x"),
lambda f: f["model"]["files"][0].update(path="../secret"),
lambda f: f["model"]["files"][0].update(size=True),
lambda f: f["model"]["files"][0].update(sha256="A" * 64),
lambda f: f["launcher"]["arguments"].append("{unknown}"),
lambda f: f["launcher"].update(path="/bin/sh"),
lambda f: f["launcher"].update(protocol="not-the-wire-protocol"),
):
invalid = copy.deepcopy(fixture)
edit(invalid)
save(invalid)
with self.subTest(invalid=invalid), self.assertRaises(ManifestError):
load_backends(root)
save(fixture)
(root / "other.json").write_text('{"id":"other","id":"other"}')
with self.assertRaises(ManifestError):
load_backends(root)
(root / "other.json").unlink()
(root / "other.json").symlink_to(DEFAULT_MANIFEST_DIR / "redux.json")
with self.assertRaises(ManifestError):
load_backends(root)
if __name__ == "__main__":
unittest.main()
+146
View File
@@ -0,0 +1,146 @@
"""Offline hash/status and CLI tests; fixtures are bytes, not models."""
import contextlib
from dataclasses import replace
import hashlib
import importlib.util
import json
import io
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "python"))
from frameyap.model_files import ModelFile, check_file, check_model, load_backends
CLI = ROOT / "scripts" / "model-status.py"
class ModelFileTests(unittest.TestCase):
def fixture(self, root):
manifests = root / "manifests"
manifests.mkdir()
backend = json.loads((ROOT / "assets/backends/redux.json").read_text())
backend["model"]["files"] = [{"path": "nested/weights.bin", "size": 7,
"sha256": hashlib.sha256(b"fixture").hexdigest()}]
(manifests / "redux.json").write_text(json.dumps(backend))
return manifests, load_backends(manifests)["redux"]
def cli(self, manifests, *arguments):
return subprocess.run([sys.executable, str(CLI), "--manifest-dir", str(manifests),
"--json", *map(str, arguments)], capture_output=True, text=True, timeout=5)
def test_status_and_cli_from_same_verifier(self):
with tempfile.TemporaryDirectory() as path:
root = Path(path)
manifests, backend = self.fixture(root)
store = root / "models"
model = store / "redux"
result = self.cli(manifests, "--list-models", "--model-dir", store)
self.assertEqual(result.returncode, 0, result.stderr)
listed = json.loads(result.stdout)
self.assertEqual(list(listed), ["models", "schema"])
self.assertEqual(listed["models"][0]["state"], "not_installed")
self.assertEqual(listed["models"][0]["reason"], "directory_missing")
self.assertEqual(listed["models"][0]["total_bytes"], 7)
self.assertEqual(self.cli(manifests, "--check-model", "redux", "--model-dir", model).returncode, 1)
(model / "nested").mkdir(parents=True)
weight = model / "nested/weights.bin"
weight.write_bytes(b"fixture")
checked = self.cli(manifests, "--check-model", "redux", "--model-dir", model)
self.assertEqual(checked.returncode, 0, checked.stderr)
self.assertEqual(json.loads(checked.stdout), {"schema": 1, "model": check_model(backend, model)})
self.assertEqual(check_file(model, backend.files[0]), (None, None))
weight.write_bytes(b"altered")
self.assertEqual(check_model(backend, model)["reason"], "hash_mismatch")
self.assertEqual(json.loads(self.cli(manifests, "--check-model", "redux", "--model-dir", model).stdout)["model"]["state"], "invalid")
weight.write_bytes(b"short")
self.assertEqual(check_model(backend, model)["reason"], "size_mismatch")
weight.unlink()
self.assertEqual(check_model(backend, model)["state"], "not_installed")
outside = root / "outside"
outside.write_bytes(b"fixture")
weight.symlink_to(outside)
self.assertEqual(check_model(backend, model)["reason"], "unsafe_file")
weight.unlink()
weight.symlink_to(root / "absent")
self.assertEqual(check_model(backend, model)["reason"], "unsafe_file")
weight.unlink()
(model / "nested").rmdir()
(model / "nested").symlink_to(root)
self.assertEqual(check_model(backend, model)["reason"], "unsafe_file")
def test_second_backend_status_without_runtime_changes(self):
with tempfile.TemporaryDirectory() as path:
root = Path(path)
manifests, _ = self.fixture(root)
independent = json.loads((manifests / "redux.json").read_text())
independent["id"] = "independent"
independent["display_name"] = "Independent backend"
independent["launcher"] = {"type": "executable", "path": "bin/independent-worker",
"arguments": ["--model", "{model_dir}", "--clip-dir", "{clip_dir}"],
"protocol": "frameyap-worker-v1"}
independent["model"]["files"] = [{"path": "alternate.bin", "size": 3,
"sha256": hashlib.sha256(b"abc").hexdigest()}]
(manifests / "independent.json").write_text(json.dumps(independent))
model_dir = root / "store/independent"
model_dir.mkdir(parents=True)
(model_dir / "alternate.bin").write_bytes(b"abc")
listed = self.cli(manifests, "--list-models", "--model-dir", root / "store")
self.assertEqual(listed.returncode, 0, listed.stderr)
models = json.loads(listed.stdout)["models"]
self.assertEqual([(model["id"], model["state"]) for model in models],
[("independent", "installed_verified"), ("redux", "not_installed")])
self.assertEqual(self.cli(manifests, "--check-model", "independent", "--model-dir", model_dir).returncode, 0)
def test_explicit_fetch_tool_reuses_shared_verifier_offline(self):
spec = importlib.util.spec_from_file_location("fetch_model", ROOT / "scripts/fetch-model.py")
fetch = importlib.util.module_from_spec(spec)
spec.loader.exec_module(fetch)
with tempfile.TemporaryDirectory() as path:
root = Path(path)
_, backend = self.fixture(root)
backend = replace(backend, files=(ModelFile("weights.bin", 7, hashlib.sha256(b"fixture").hexdigest()),))
model = root / "local-model"
# The Redux-only tool uses this small fixture manifest here; all network
# calls are mocked, never real downloads.
with patch.object(fetch, "load_backends", return_value={"redux": backend}), \
patch.object(fetch.urllib.request, "urlopen", return_value=io.BytesIO(b"fixture")) as urlopen, \
patch.object(sys, "argv", ["fetch-model.py", "--destination", str(model)]):
with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
fetch.main()
urlopen.assert_called_once()
self.assertEqual(check_model(backend, model)["state"], "installed_verified")
fetch.main()
urlopen.assert_called_once()
(model / "weights.bin").write_bytes(b"changed")
with self.assertRaises(SystemExit):
fetch.main()
urlopen.assert_called_once()
def test_missing_manifest_unknown_id_and_no_runtime_imports(self):
with tempfile.TemporaryDirectory() as path:
manifests, _ = self.fixture(Path(path))
result = self.cli(manifests, "--check-model", "unknown", "--model-dir", path)
self.assertEqual(result.returncode, 2)
self.assertEqual(result.stdout, "")
result = self.cli(Path(path) / "absent", "--list-models")
self.assertEqual(result.returncode, 2)
result = self.cli(manifests, "--list-models")
self.assertEqual(result.returncode, 0)
self.assertEqual(json.loads(result.stdout)["models"][0]["state"], "unknown")
self.assertEqual(json.loads(result.stdout)["models"][0]["reason"], "model_dir_unspecified")
# This subprocess never imports inference packages even if not installed.
result = subprocess.run([sys.executable, "-c", "import sys; import frameyap.model_files; "
"assert not {'moondream', 'torch', 'numpy'} & set(sys.modules)"],
env={**os.environ, "PYTHONPATH": str(ROOT / "python")},
capture_output=True, timeout=5)
self.assertEqual(result.returncode, 0, result.stderr)
if __name__ == "__main__":
unittest.main()
+6 -2
View File
@@ -238,14 +238,18 @@ sys.exit(worker.main(sys.argv[1:]))
def test_pinned_model_hashes_and_symlinks(self):
import hashlib
from frameyap.model_files import Backend, ModelFile
with tempfile.TemporaryDirectory() as path:
root = Path(path)
(root / "model.safetensors").write_bytes(b"fixture")
digest = hashlib.sha256(b"fixture").hexdigest()
with patch.object(worker, "FILES", {"model.safetensors": (7, digest)}):
backend = Backend("redux", "Redux", {}, "https://example.test", "pinned",
(ModelFile("model.safetensors", 7, digest),), "source",
"CC-BY-4.0", "license", "cpu", "none")
with patch.object(worker, "load_backends", return_value={"redux": backend}):
self.assertEqual(worker.local_model(path), path)
(root / "model.safetensors").write_bytes(b"changed")
with self.assertRaisesRegex(ValueError, "pinned revision"):
with self.assertRaisesRegex(ValueError, "hash_mismatch"):
worker.local_model(path)
(root / "model.safetensors").unlink()
(root / "other").write_bytes(b"fixture")