test: qualify immutable models and Phase 9 regression
MariaDB persistence qualification / mariadb (push) Has been cancelled

This commit is contained in:
cesnimda
2026-10-25 13:43:50 +01:00
parent 30e251a593
commit b48f25c4ff
33 changed files with 67462 additions and 6 deletions
@@ -0,0 +1,17 @@
{
"observedUtc": "2026-10-25T11:37:24.319354+00:00",
"status": "PASS",
"sourceImageId": "sha256:4a04ca68fa40b3ec61cb61187b339a4d2895b375b962999e974d75556b5b1331",
"package": "tesseract-ocr-eng",
"version": "1:4.1.0-2",
"artifact": "/usr/share/tesseract-ocr/5/tessdata/eng.traineddata",
"bytes": 4113088,
"sha256": "7d4322bd2a7749724879683fc3912cb542f19906c83bcc1a52132556427170b2",
"licenseEvidence": {
"path": "docs/redesign/phase-9/p9-sec-02/model/OCR_COPYRIGHT.txt",
"sha256": "33fb8161898e34fcf5c6b8585d6be97642d7e8f34a922e783c6c5c970d0b09ad"
},
"exitCode": 0,
"containerAbsent": true,
"scope": "Installed Debian English OCR training data in the exact qualified local runtime image; source package copyright retained. No new OCR model download."
}
@@ -0,0 +1,32 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: tessdata_fast
Upstream-Contact: Ray Smith <theraysmith@users.sourceforge.net>
Source: https://github.com/tesseract-ocr/tessdata_fast/
Files: *
Copyright: 1988-1995 Hewlett Packard Company.
2006-2022 Google Inc.
License: Apache-2.0
Files: debian/*
Copyright: 2007-2009 Jeffrey Ratcliffe <Jeffrey.Ratcliffe@gmail.com>
2017-2019 Jeff Breidenbach <jab@debian.org>
2017-2022 Alexander Pozdnyakov <almipo@mail.ru>
License: Apache-2.0
License: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the license at
.
http://www.apache.org/licenses/LICENSE-2.0
.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
.
On Debian systems, the complete text of the Apache 2.0 license can be
found in the file
`/usr/share/common-licenses/Apache-2.0`.
@@ -0,0 +1,12 @@
"""Owned local qualification cleanup evidence."""
import subprocess
def container_absent(name):
try:
query=subprocess.run(['rtk','proxy','docker','ps','--all','--no-trunc','--filter',f'name=^/{name}$','--format','{{.ID}}'],capture_output=True,text=True,timeout=5)
except (OSError,subprocess.TimeoutExpired) as error:
raise RuntimeError('Cleanup daemon query unavailable or timed out.') from error
if query.returncode != 0:
raise RuntimeError('Cleanup daemon query failed; absence is unverified.')
return not query.stdout.strip()
@@ -0,0 +1,25 @@
{
"path": ".artifacts/p9-sec-02/small-fixtures.zip",
"bytes": 9895179,
"sha256": "a1aff347e5f08f82e8ea7176d71d00e7f83ea5735acbdc4e3bf02c73deacb643",
"files": {
"safetensors/README.md": "e6114f9ddddd7aec5e9858b9849d1b53dc42b596420888b82fbf3f8defb64bf7",
"safetensors/reference-distilbart-config.json": "028d72a28b90a369eff3f99ceb585fbd2ababf070e8577d72c315757a49b8917",
"safetensors/vocab.json": "06b4d46c8e752d410213d9548eb27a54db70fda0319b6271fb8d59dead5e1cab",
"safetensors/merges.txt": "1ce1664773c50f3e0cc8842619a93edc4624525b728b188a9e0be33b7726adc5",
"safetensors/tokenizer_config.json": "5e04eb606e3a1583530a42e36c2a6b6615c86f34fe77e44d9ddeb43ff940931f",
"safetensors/config.json": "563b819e93f153a49d6364d5c90878eec01ef78f99aae0d0c8c7ccce49e517a8",
"safetensors/generation_config.json": "fdf2a5aa742aa3bd7fcf78a14d9aae0c599e55610e7856a35a4e89a38fed50f7",
"safetensors/model.safetensors": "35ca2b5d2c6a28696cadd0fd7dfe483d499497e315db28dfa5a509bf13da7a68",
"safetensors/generate.py": "006cd1354c98e72ce9a7f1f57c5b3e3fd1cf16836dcb4508ca9f9930b4777fa5",
"weights-only/config.json": "b4f823ea324a93c355ae2ae2447bed10fcdb769070eff435f175a6738f7d3c34",
"weights-only/generate-bin.py": "5d1f53ea37cc199d876b27731a29dea74b4df7fed7151f67a49b848993517052",
"weights-only/generation_config.json": "fdf2a5aa742aa3bd7fcf78a14d9aae0c599e55610e7856a35a4e89a38fed50f7",
"weights-only/merges.txt": "1ce1664773c50f3e0cc8842619a93edc4624525b728b188a9e0be33b7726adc5",
"weights-only/pytorch_model.bin": "95d578c5706c9256a2f299a4288c0cdcae0fd303fa27e4474c34300f51f195cd",
"weights-only/README.md": "e6114f9ddddd7aec5e9858b9849d1b53dc42b596420888b82fbf3f8defb64bf7",
"weights-only/tokenizer_config.json": "5e04eb606e3a1583530a42e36c2a6b6615c86f34fe77e44d9ddeb43ff940931f",
"weights-only/vocab.json": "06b4d46c8e752d410213d9548eb27a54db70fda0319b6271fb8d59dead5e1cab"
},
"scope": "Local retained replay capsule only. No model weights committed, uploaded or published. Verify archive SHA and per-file committed manifest before restoring to NEW isolated test directories. Different generator/runtime bytes require a new manifest and qualification."
}
+11 -3
View File
@@ -6,6 +6,7 @@ import subprocess
import sys
import time
import uuid
from cleanup import container_absent
ROOT=Path(__file__).resolve().parents[5]
HERE=Path(__file__).resolve().parent
@@ -37,14 +38,21 @@ with (out/'process.log').open('w',encoding='utf-8') as log:
try:code=subprocess.run(['rtk','proxy',*args],cwd=ROOT,stdout=log,stderr=subprocess.STDOUT,timeout=180).returncode
except subprocess.TimeoutExpired:
code=124
check=subprocess.run(['rtk','proxy','docker','inspect','--format','{{index .Config.Labels "jobtracker.qualification"}}',name],capture_output=True,text=True)
if check.returncode==0 and check.stdout.strip()=='p9-sec02':subprocess.run(['rtk','proxy','docker','kill',name],capture_output=True)
try:
check=subprocess.run(['rtk','proxy','docker','inspect','--format','{{index .Config.Labels "jobtracker.qualification"}}',name],capture_output=True,text=True,timeout=5)
if check.returncode==0 and check.stdout.strip()=='p9-sec02':subprocess.run(['rtk','proxy','docker','kill',name],capture_output=True,timeout=10)
except (OSError,subprocess.TimeoutExpired):pass
deadline=time.monotonic()+5
cleanup_error=None
while True:
absent=subprocess.run(['rtk','proxy','docker','inspect',name],capture_output=True).returncode!=0
try:absent=container_absent(name)
except RuntimeError as error:
absent=None;cleanup_error=str(error);break
if absent or time.monotonic()>=deadline:break
time.sleep(.1)
record={'lane':lane,'attempt':attempt,'startedUtc':started,'finishedUtc':datetime.datetime.now(datetime.timezone.utc).isoformat(),'exitCode':code,'command':args,'localImageId':image,'runtimeImageId':runtime['runtimeImageId'],'containerAbsent':absent,'network':'none','readOnly':True,'user':'65534:65534','memoryLimitBytes':2147483648,'cpuLimit':2,'pidsLimit':128,'wallClockDeadlineSeconds':180,'scope':'Actual locally built source image plus exact test-only extras; no app source mount, production cache, external network, registry push or deployment.'}
record['cleanupStatus']='ABSENT' if absent else 'UNKNOWN' if absent is None else 'PRESENT'
record['cleanupError']=cleanup_error
(out/'process.json').write_text(json.dumps(record,indent=2)+'\n')
print(json.dumps(record))
print((out/'process.log').read_text(encoding='utf-8',errors='replace')[-2500:].encode('ascii','replace').decode())
@@ -0,0 +1,39 @@
import subprocess
import unittest
from unittest.mock import patch
from cleanup import container_absent
class PositiveCleanupEvidence(unittest.TestCase):
def query(self, absent, available):
def run(args, **options):
if not available: return subprocess.CompletedProcess(args,1,'','daemon unavailable')
if 'inspect' in args: return subprocess.CompletedProcess(args,1 if absent else 0,'','no such object' if absent else '')
return subprocess.CompletedProcess(args,0,'' if absent else '0123456789abcdef\n','')
return run
def test_successful_daemon_query_proves_absence(self):
with patch('cleanup.subprocess.run',side_effect=self.query(True,True)):
self.assertTrue(container_absent('jt-sec02-owned'))
def test_present_container_is_not_removed(self):
with patch('cleanup.subprocess.run',side_effect=self.query(False,True)):
self.assertFalse(container_absent('jt-sec02-owned'))
def test_daemon_failure_cannot_certify_cleanup(self):
with patch('cleanup.subprocess.run',side_effect=self.query(True,False)):
with self.assertRaises(RuntimeError): container_absent('jt-sec02-owned')
def test_timeout_is_unknown_and_calls_are_bounded(self):
with patch('cleanup.subprocess.run',side_effect=subprocess.TimeoutExpired('docker',5)) as query:
with self.assertRaises(RuntimeError): container_absent('jt-sec02-owned')
self.assertEqual(query.call_args.kwargs['timeout'],5)
def test_exact_owned_name_filter_is_used(self):
with patch('cleanup.subprocess.run',side_effect=self.query(True,True)) as query:
self.assertTrue(container_absent('jt-sec02-owned'))
self.assertIn('name=^/jt-sec02-owned$',query.call_args.args[0])
self.assertNotIn('inspect',query.call_args.args[0])
if __name__=='__main__':unittest.main()