fix: pin model revisions and quarantine MariaDB CI routing
MariaDB persistence qualification / mariadb (push) Has been cancelled
MariaDB persistence qualification / mariadb (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
{
|
||||
"safetensors": {
|
||||
"schemaVersion": 1,
|
||||
"observedUtc": "2026-10-25T06:29:24.469337+00:00",
|
||||
"fixture": "jobjakt-p9-sec02-bart-cpu",
|
||||
"scope": "Real BART weights/tokenizer/native CPU inference compatibility fixture. Random/untrained output is not production DistilBART inference, semantic quality, full-model memory/capacity or AI product qualification.",
|
||||
"sourceRepository": "sshleifer/distilbart-cnn-12-6",
|
||||
"sourceRevision": "a4f8f3ea906ed274767e9906dbaede7531d660ff",
|
||||
"upstreamLicense": "Apache-2.0 verified in pinned model card",
|
||||
"generation": {
|
||||
"torch": "2.6.0",
|
||||
"transformers": "4.48.3",
|
||||
"parameters": 842704,
|
||||
"seed": 73421,
|
||||
"device": "cpu",
|
||||
"kind": "Locally generated random-weight BART, not trained DistilBART or a quality fixture"
|
||||
},
|
||||
"files": [
|
||||
{
|
||||
"file": "README.md",
|
||||
"url": "https://huggingface.co/sshleifer/distilbart-cnn-12-6/resolve/a4f8f3ea906ed274767e9906dbaede7531d660ff/README.md",
|
||||
"gitBlobId": "ab24d89a622b44fba741e4383213ad300fc2345a",
|
||||
"bytes": 1705,
|
||||
"sha256": "e6114f9ddddd7aec5e9858b9849d1b53dc42b596420888b82fbf3f8defb64bf7",
|
||||
"license": "Apache-2.0, pinned upstream model-card declaration"
|
||||
},
|
||||
{
|
||||
"file": "reference-distilbart-config.json",
|
||||
"url": "https://huggingface.co/sshleifer/distilbart-cnn-12-6/resolve/a4f8f3ea906ed274767e9906dbaede7531d660ff/config.json",
|
||||
"gitBlobId": "fade82e22ec4f4b61f6f2eb3986ce4a64a8e23df",
|
||||
"bytes": 1802,
|
||||
"sha256": "028d72a28b90a369eff3f99ceb585fbd2ababf070e8577d72c315757a49b8917",
|
||||
"license": "Apache-2.0, pinned upstream model-card declaration"
|
||||
},
|
||||
{
|
||||
"file": "vocab.json",
|
||||
"url": "https://huggingface.co/sshleifer/distilbart-cnn-12-6/resolve/a4f8f3ea906ed274767e9906dbaede7531d660ff/vocab.json",
|
||||
"gitBlobId": "0a39732b2d8be8e493cab3da68b68cc3e28221de",
|
||||
"bytes": 898822,
|
||||
"sha256": "06b4d46c8e752d410213d9548eb27a54db70fda0319b6271fb8d59dead5e1cab",
|
||||
"license": "Apache-2.0, pinned upstream model-card declaration"
|
||||
},
|
||||
{
|
||||
"file": "merges.txt",
|
||||
"url": "https://huggingface.co/sshleifer/distilbart-cnn-12-6/resolve/a4f8f3ea906ed274767e9906dbaede7531d660ff/merges.txt",
|
||||
"gitBlobId": "226b0752cac7789c48f0cb3ec53eda48b7be36cc",
|
||||
"bytes": 456318,
|
||||
"sha256": "1ce1664773c50f3e0cc8842619a93edc4624525b728b188a9e0be33b7726adc5",
|
||||
"license": "Apache-2.0, pinned upstream model-card declaration"
|
||||
},
|
||||
{
|
||||
"file": "tokenizer_config.json",
|
||||
"url": "https://huggingface.co/sshleifer/distilbart-cnn-12-6/resolve/a4f8f3ea906ed274767e9906dbaede7531d660ff/tokenizer_config.json",
|
||||
"gitBlobId": "be4d21d94f3b4687e5a54d84bf6ab46ed0f8defd",
|
||||
"bytes": 26,
|
||||
"sha256": "5e04eb606e3a1583530a42e36c2a6b6615c86f34fe77e44d9ddeb43ff940931f",
|
||||
"license": "Apache-2.0, pinned upstream model-card declaration"
|
||||
},
|
||||
{
|
||||
"file": "config.json",
|
||||
"bytes": 1025,
|
||||
"sha256": "563b819e93f153a49d6364d5c90878eec01ef78f99aae0d0c8c7ccce49e517a8",
|
||||
"source": "Synthetic seeded BART generated locally using Apache-2.0 Transformers architecture; no downloaded weights"
|
||||
},
|
||||
{
|
||||
"file": "generation_config.json",
|
||||
"bytes": 200,
|
||||
"sha256": "fdf2a5aa742aa3bd7fcf78a14d9aae0c599e55610e7856a35a4e89a38fed50f7",
|
||||
"source": "Synthetic seeded BART generated locally using Apache-2.0 Transformers architecture; no downloaded weights"
|
||||
},
|
||||
{
|
||||
"file": "model.safetensors",
|
||||
"bytes": 3577252,
|
||||
"sha256": "35ca2b5d2c6a28696cadd0fd7dfe483d499497e315db28dfa5a509bf13da7a68",
|
||||
"source": "Synthetic seeded BART generated locally using Apache-2.0 Transformers architecture; no downloaded weights"
|
||||
},
|
||||
{
|
||||
"file": "generate.py",
|
||||
"bytes": 850,
|
||||
"sha256": "006cd1354c98e72ce9a7f1f57c5b3e3fd1cf16836dcb4508ca9f9930b4777fa5",
|
||||
"source": "Synthetic seeded BART generated locally using Apache-2.0 Transformers architecture; no downloaded weights"
|
||||
}
|
||||
],
|
||||
"downloadBytes": 1358673,
|
||||
"weightBytes": 3577252,
|
||||
"fullModelAbsent": true,
|
||||
"fullModelRequiredWeightBytes": 1222317369,
|
||||
"fullModelExpectedSha256": "3bac65d18c99463302d12ca75c2220ea714f9c81ce235f205fa818efe71df6ea",
|
||||
"cachePolicy": "Isolated package test cache only; never production/Ollama cache. Verify every file before loading; load offline/read-only; no floating revision."
|
||||
},
|
||||
"weights-only": {
|
||||
"schemaVersion": 1,
|
||||
"observedUtc": "2026-10-25T06:29:24.469337+00:00",
|
||||
"fixture": "jobjakt-p9-sec02-bart-bin-cpu",
|
||||
"scope": "Same locally generated synthetic BART state_dict in the selected production model serialization family, exercising real weights_only PyTorch loading. No third-party downloaded pickle; not full DistilBART/quality qualification.",
|
||||
"sourceRepository": "sshleifer/distilbart-cnn-12-6",
|
||||
"sourceRevision": "a4f8f3ea906ed274767e9906dbaede7531d660ff",
|
||||
"upstreamLicense": "Apache-2.0 verified in pinned model card",
|
||||
"generation": {
|
||||
"torch": "2.6.0",
|
||||
"transformers": "4.48.3",
|
||||
"parameters": 842704,
|
||||
"seed": 73421,
|
||||
"device": "cpu",
|
||||
"kind": "Locally generated random-weight BART, not trained DistilBART or a quality fixture"
|
||||
},
|
||||
"files": [
|
||||
{
|
||||
"file": "config.json",
|
||||
"bytes": 1124,
|
||||
"sha256": "b4f823ea324a93c355ae2ae2447bed10fcdb769070eff435f175a6738f7d3c34",
|
||||
"source": "Copied verified tokenizer/config or locally generated synthetic state_dict"
|
||||
},
|
||||
{
|
||||
"file": "generate-bin.py",
|
||||
"bytes": 411,
|
||||
"sha256": "5d1f53ea37cc199d876b27731a29dea74b4df7fed7151f67a49b848993517052",
|
||||
"source": "Copied verified tokenizer/config or locally generated synthetic state_dict"
|
||||
},
|
||||
{
|
||||
"file": "generation_config.json",
|
||||
"bytes": 200,
|
||||
"sha256": "fdf2a5aa742aa3bd7fcf78a14d9aae0c599e55610e7856a35a4e89a38fed50f7",
|
||||
"source": "Copied verified tokenizer/config or locally generated synthetic state_dict"
|
||||
},
|
||||
{
|
||||
"file": "merges.txt",
|
||||
"bytes": 456318,
|
||||
"sha256": "1ce1664773c50f3e0cc8842619a93edc4624525b728b188a9e0be33b7726adc5",
|
||||
"source": "Copied verified tokenizer/config or locally generated synthetic state_dict"
|
||||
},
|
||||
{
|
||||
"file": "pytorch_model.bin",
|
||||
"bytes": 3588356,
|
||||
"sha256": "95d578c5706c9256a2f299a4288c0cdcae0fd303fa27e4474c34300f51f195cd",
|
||||
"source": "Copied verified tokenizer/config or locally generated synthetic state_dict"
|
||||
},
|
||||
{
|
||||
"file": "README.md",
|
||||
"bytes": 1705,
|
||||
"sha256": "e6114f9ddddd7aec5e9858b9849d1b53dc42b596420888b82fbf3f8defb64bf7",
|
||||
"source": "Copied verified tokenizer/config or locally generated synthetic state_dict"
|
||||
},
|
||||
{
|
||||
"file": "tokenizer_config.json",
|
||||
"bytes": 26,
|
||||
"sha256": "5e04eb606e3a1583530a42e36c2a6b6615c86f34fe77e44d9ddeb43ff940931f",
|
||||
"source": "Copied verified tokenizer/config or locally generated synthetic state_dict"
|
||||
},
|
||||
{
|
||||
"file": "vocab.json",
|
||||
"bytes": 898822,
|
||||
"sha256": "06b4d46c8e752d410213d9548eb27a54db70fda0319b6271fb8d59dead5e1cab",
|
||||
"source": "Copied verified tokenizer/config or locally generated synthetic state_dict"
|
||||
}
|
||||
],
|
||||
"downloadBytes": 1358673,
|
||||
"weightBytes": 3588356,
|
||||
"fullModelAbsent": true,
|
||||
"fullModelRequiredWeightBytes": 1222317369,
|
||||
"fullModelExpectedSha256": "3bac65d18c99463302d12ca75c2220ea714f9c81ce235f205fa818efe71df6ea",
|
||||
"cachePolicy": "Isolated package test cache only; never production/Ollama cache. Verify every file before loading; load offline/read-only; no floating revision."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
"""Actual offline CPU inference; synthetic weights test compatibility, never quality."""
|
||||
from pathlib import Path
|
||||
import importlib.metadata
|
||||
import json
|
||||
import os
|
||||
import resource
|
||||
import sys
|
||||
import time
|
||||
from verify import verify_fixture
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
manifests = json.loads((HERE / 'ARTIFACTS.json').read_text())
|
||||
kind = sys.argv[1]
|
||||
fixture = verify_fixture(Path(sys.argv[2]), manifests[kind])
|
||||
assert os.environ['HF_HUB_OFFLINE'] == os.environ['TRANSFORMERS_OFFLINE'] == '1'
|
||||
os.environ.pop('AI_SERVICE_SKIP_MODEL_LOAD', None)
|
||||
os.environ.pop('AI_SERVICE_EAGER_MODEL_LOAD', None)
|
||||
os.environ['AI_SERVICE_TOKEN'] = 'p9-local-qualification-only'
|
||||
sys.path.insert(0, '/app')
|
||||
import torch
|
||||
import app
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
torch.set_num_threads(1)
|
||||
started = time.monotonic()
|
||||
# Only this isolated qualification process selects synthetic weights; no inference is mocked.
|
||||
app.MODEL_NAME = str(fixture)
|
||||
assert app._ensure_runtime_loaded(), app.MODEL_LOAD_ERROR
|
||||
assert app.model.__class__.__name__ == 'BartForConditionalGeneration'
|
||||
assert str(app.device) == 'cpu' and not app.GPU_AVAILABLE
|
||||
text = 'Åse Østby builds accessible Python and C# applications for reliable Norwegian career workflows.'
|
||||
encoded = app.tokenizer(text, return_tensors='pt')
|
||||
assert app.tokenizer.decode(encoded.input_ids[0], skip_special_tokens=True) == text
|
||||
summary = app._model_summarize(text, 24, 8)
|
||||
assert isinstance(summary, str) and summary
|
||||
client = TestClient(app.app)
|
||||
assert client.get('/health').json()['model_loaded'] is True
|
||||
assert client.post('/summarize', json={'text': text}).status_code == 401
|
||||
headers = {'X-Ai-Service-Token': 'p9-local-qualification-only'}
|
||||
body = {'text': text, 'max_length': 24, 'min_length': 8}
|
||||
first = client.post('/summarize', json=body, headers=headers)
|
||||
second = client.post('/summarize', json=body, headers=headers)
|
||||
assert first.status_code == second.status_code == 200, first.text
|
||||
assert first.json()['cached'] is False and second.json()['cached'] is True
|
||||
assert first.json()['summary'] == second.json()['summary']
|
||||
verify_fixture(fixture, manifests[kind])
|
||||
elapsed = time.monotonic() - started
|
||||
assert elapsed < 60
|
||||
print(json.dumps({'status': 'PASS', 'kind': kind, 'scope': 'Real synthetic BART/tokenizer/native CPU generation plus sidecar summary/auth/cache contract; no full DistilBART or semantic-quality claim.', 'modelClass': app.model.__class__.__name__, 'device': str(app.device), 'cudaAvailable': torch.cuda.is_available(), 'seconds': elapsed, 'maxRssKiB': resource.getrusage(resource.RUSAGE_SELF).ru_maxrss, 'versions': {name: importlib.metadata.version(name) for name in ['torch','transformers','filelock','huggingface-hub','tokenizers','safetensors']}, 'summary': summary, 'cacheRepeatedRead': True, 'fixtureUnchanged': True}), flush=True)
|
||||
@@ -0,0 +1,40 @@
|
||||
from pathlib import Path
|
||||
import copy
|
||||
import hashlib
|
||||
import tempfile
|
||||
import unittest
|
||||
from verify import verify_fixture
|
||||
|
||||
|
||||
class ArtifactIntegrity(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.directory = Path(self.temp.name)
|
||||
(self.directory / 'model.safetensors').write_bytes(b'known fixture')
|
||||
self.manifest = {'files': [{'file': 'model.safetensors', 'bytes': 13, 'sha256': hashlib.sha256(b'known fixture').hexdigest()}]}
|
||||
|
||||
def test_accepts_exact_known_content(self):
|
||||
self.assertEqual(verify_fixture(self.directory, self.manifest), self.directory.resolve())
|
||||
|
||||
def test_rejects_same_size_corruption(self):
|
||||
(self.directory / 'model.safetensors').write_bytes(b'wrong fixture')
|
||||
with self.assertRaises(ValueError): verify_fixture(self.directory, self.manifest)
|
||||
|
||||
def test_rejects_missing_weight(self):
|
||||
(self.directory / 'model.safetensors').unlink()
|
||||
with self.assertRaises(ValueError): verify_fixture(self.directory, self.manifest)
|
||||
|
||||
def test_rejects_traversal_and_duplicate_identity(self):
|
||||
for value in ['../model.safetensors', '/model.safetensors']:
|
||||
manifest = copy.deepcopy(self.manifest); manifest['files'][0]['file'] = value
|
||||
with self.assertRaises(ValueError): verify_fixture(self.directory, manifest)
|
||||
self.manifest['files'] *= 2
|
||||
with self.assertRaises(ValueError): verify_fixture(self.directory, self.manifest)
|
||||
|
||||
def test_rejects_unlisted_model_code(self):
|
||||
(self.directory / 'modeling_unreviewed.py').write_text('raise RuntimeError()')
|
||||
with self.assertRaises(ValueError): verify_fixture(self.directory, self.manifest)
|
||||
|
||||
|
||||
if __name__ == '__main__': unittest.main()
|
||||
@@ -0,0 +1,21 @@
|
||||
"""Verify committed qualification identities before loading an isolated model fixture."""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
|
||||
|
||||
def verify_fixture(directory, manifest):
|
||||
directory = Path(directory).resolve()
|
||||
expected = set()
|
||||
for entry in manifest['files']:
|
||||
name = entry['file']
|
||||
if Path(name).name != name or name in ('.', '..'):
|
||||
raise ValueError('Artifact name escapes fixture directory.')
|
||||
file = directory / name
|
||||
if name in expected or file.is_symlink() or not file.is_file():
|
||||
raise ValueError('Missing, duplicate or linked model artifact: ' + name)
|
||||
expected.add(name)
|
||||
if file.stat().st_size != entry['bytes'] or hashlib.sha256(file.read_bytes()).hexdigest() != entry['sha256']:
|
||||
raise ValueError('Model artifact integrity mismatch: ' + name)
|
||||
if not expected or {p.name for p in directory.iterdir()} - expected - {'manifest.json', 'generation.log'}:
|
||||
raise ValueError('Unexpected artifact or empty manifest.')
|
||||
return directory
|
||||
Reference in New Issue
Block a user