136 lines
8.4 KiB
Python
136 lines
8.4 KiB
Python
"""Offline receipt/source/index verifier. Does not launch archived scripts or apps."""
|
|
from pathlib import Path
|
|
import argparse
|
|
import copy
|
|
import hashlib
|
|
import json
|
|
import subprocess
|
|
|
|
|
|
def require(value, why):
|
|
if not value:
|
|
raise RuntimeError(why)
|
|
|
|
|
|
def identity(data):
|
|
return {'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--index', action='store_true', help='Also verify staged evidence bytes exactly')
|
|
parser.add_argument('--current-source', action='store_true', help='Also require current source bytes to match packaging snapshot')
|
|
args = parser.parse_args()
|
|
packet = Path(__file__).resolve().parent
|
|
root = next(p for p in packet.parents if (p / '.git').exists())
|
|
read = lambda p: json.loads((packet / p).read_text(encoding='utf-8-sig'))
|
|
inventory = read('evidence-files.json')
|
|
actual = {p.relative_to(packet).as_posix() for p in packet.rglob('*') if p.is_file()}
|
|
require(actual == set(inventory) | {'evidence-files.json'}, 'Packet filename set mismatch')
|
|
for name, expected in inventory.items():
|
|
require(identity((packet / name).read_bytes()) == expected, 'Packet identity mismatch: ' + name)
|
|
if args.index:
|
|
names = sorted(actual)
|
|
prefix = packet.relative_to(root).as_posix()
|
|
queries = ''.join(':' + prefix + '/' + name + '\n' for name in names).encode()
|
|
raw = subprocess.check_output(['git', 'cat-file', '--batch'], input=queries, cwd=root)
|
|
offset = 0
|
|
for name in names:
|
|
end = raw.index(b'\n', offset)
|
|
header = raw[offset:end].split()
|
|
require(len(header) == 3 and header[1] == b'blob', 'Indexed evidence missing: ' + name)
|
|
size = int(header[2]); start = end + 1
|
|
require(raw[start:start+size] == (packet / name).read_bytes(), 'Indexed bytes differ: ' + name)
|
|
require(raw[start+size:start+size+1] == b'\n', 'Malformed batch response')
|
|
offset = start + size + 1
|
|
require(offset == len(raw), 'Extra batch bytes')
|
|
source = read('source-inputs.json')
|
|
tree = subprocess.check_output(['git', 'ls-tree', '-r', '-z', source['source']], cwd=root)
|
|
blobs = {}
|
|
for entry in tree.split(b'\0'):
|
|
if entry:
|
|
metadata, path = entry.split(b'\t', 1)
|
|
blobs[path.decode()] = metadata.split()[2].decode()
|
|
for name, expected in source['files'].items():
|
|
require(blobs.get(name) == expected['git_blob'], 'Pinned source blob mismatch: ' + name)
|
|
if args.current_source:
|
|
require(identity((root / name).read_bytes()) == {k: expected[k] for k in ['bytes', 'sha256']},
|
|
'Current source differs from packaging snapshot: ' + name)
|
|
package = read('package/package.json')
|
|
require(package['build_evidence']['evidence'] == read('build-evidence.json'), 'Packaged evidence differs')
|
|
extraction = read('extraction-check.json')
|
|
require(extraction['status'] == 'PASS' and extraction['files'] == 565, 'Extraction receipt mismatch')
|
|
require(extraction['archive_sha256'] == '8b4e353e075f22350a07794ee47a02cf85725501223299cf367134aa1f5b63b7', 'Archive identity mismatch')
|
|
for name, digest in package['input_sha256'].items():
|
|
require(extraction['sha256'][name] == digest, 'Package/extraction input mismatch: ' + name)
|
|
require(read('server-smoke/result.json')['status'] == 'PASS'
|
|
and len(read('server-smoke/result.json')['checks']) == 23, 'Server smoke receipt mismatch')
|
|
for folder in ['native46', 'reopen46']:
|
|
final = read('qa/' + folder + '/finished.json')
|
|
require(final['exit_code'] == 0 and final['package_files_unchanged'], 'Native exit mismatch')
|
|
for stream in ['stdout', 'stderr']:
|
|
require(final[stream + '_bytes'] == (packet / 'qa' / folder / (stream + '.log')).stat().st_size == 0,
|
|
'Native stream mismatch')
|
|
oracle = read('native-model-oracle/result.json')
|
|
require(oracle['status'] == 'PASS' and oracle['server_exit'] == 1, 'Model oracle process receipt mismatch')
|
|
models = []
|
|
sources = []
|
|
for post, get in [(2, 3), (4, 5), (6, 7)]:
|
|
req = read(f'native-model-oracle/{post:02d}.request.json')
|
|
env = read(f'native-model-oracle/{get:02d}.response.json')
|
|
require(env['source_project'] == req and len(req['files']) == 30, 'Complete source envelope mismatch')
|
|
sources.append(req['files']); models.append(env['model'])
|
|
target = '61f7bbfb-74a4-5540-9182-2583dfb27a49'
|
|
expected_model = copy.deepcopy(models[0])
|
|
require(expected_model['elements'][target]['name'] == 'Assess pressure alarm', 'Pristine model name changed')
|
|
expected_model['elements'][target]['name'] = 'Assess pressure alarm Package46'
|
|
require(models[1] == models[2] == expected_model, 'Full model delta mismatch')
|
|
baseline = read('qa/native46/baseline.json')
|
|
pristine = {name: text.encode('utf-8') for name, text in sources[0].items()}
|
|
for p in (packet / 'reference-companions').rglob('*'):
|
|
if p.is_file():
|
|
pristine[p.relative_to(packet / 'reference-companions').as_posix()] = p.read_bytes()
|
|
hashed = lambda files: {name: identity(data) for name, data in files.items()}
|
|
require(len(pristine) == 37 and hashed(pristine) == baseline['baseline'], 'Complete pristine byte set mismatch')
|
|
changed = pristine.copy()
|
|
dependency = baseline['external_dependency']
|
|
old = b'oa_activity oa_assess "Assess pressure alarm" id "61f7bbfb-74a4-5540-9182-2583dfb27a49"'
|
|
new = b'oa_activity oa_assess "Assess pressure alarm Package46" id "61f7bbfb-74a4-5540-9182-2583dfb27a49"'
|
|
require(changed[dependency].count(old) == 1, 'Declared source edit is ambiguous')
|
|
changed[dependency] = changed[dependency].replace(old, new)
|
|
require({k: v.decode('utf-8') for k, v in changed.items() if k.endswith('.arcadia')} == sources[1] == sources[2],
|
|
'Complete saved source has undeclared delta')
|
|
external = pristine.copy()
|
|
external[dependency] += baseline['external_comment'].encode('utf-8')
|
|
for phase in ['collision', 'saved-a', 'saved-c', 'external-before', 'inject', 'external-refused', 'restore', 'external-recovered']:
|
|
expected = changed if phase in ['saved-a', 'saved-c'] else external if phase in ['inject', 'external-refused'] else pristine
|
|
result = read('qa/native46/' + phase + '-oracle.json')
|
|
require(result['status'] == 'PASS' and result['files'] == hashed(expected), 'Phase byte oracle mismatch: ' + phase)
|
|
reopened = read('qa/reopen46/bytes-oracle.json')
|
|
require(reopened['status'] == 'PASS' and reopened['files'] == hashed(changed)
|
|
== read('qa/native46/saved-c-oracle.json')['files'], 'Reopen complete byte oracle mismatch')
|
|
validation = read('model-warnings/result.json')
|
|
require(validation['exit'] == 1 and len(validation['records']) == 2, 'Validation process/record count mismatch')
|
|
warnings = {r['label']: r['response'] for r in validation['records']}
|
|
require(set(warnings) == {'pristine', 'saved'}, 'Validation labels mismatch')
|
|
for label, response in warnings.items():
|
|
require(response == read('model-warnings/' + label + '.json') and response['valid'], 'Raw validation response mismatch')
|
|
prior = warnings['pristine']['issues']
|
|
drift = [i for i in warnings['saved']['issues'] if i['code'] == 'trace_drift']
|
|
require(len(prior) == 1 and prior[0]['code'] == 'work_without_behavior_context'
|
|
and prior[0]['object_id'] == '4149a872-3440-59de-9879-8981e78d27ab'
|
|
and prior[0]['severity'] == 'warning', 'Pristine context warning changed')
|
|
require(len(drift) == 1 and drift[0]['field'] == 'baseline'
|
|
and drift[0]['object_id'] == '5309f955-53fd-5ff4-9c0a-91737e947341'
|
|
and drift[0]['severity'] == 'warning' and 'OaChanged' in drift[0]['message']
|
|
and [i for i in warnings['saved']['issues'] if i['code'] != 'trace_drift'] == prior,
|
|
'Saved validation has an unexpected issue delta')
|
|
require(len(list((packet / 'qa/native46/captures').glob('*.jpg'))) == 19, 'Capture count differs')
|
|
print(json.dumps({'status': 'PASS', 'evidence_files': len(inventory), 'source_files': len(source['files']),
|
|
'index_checked': args.index, 'current_source_checked': args.current_source,
|
|
'scope': 'Offline identity and complete source/model receipt consistency; no repeated native or compiler execution.'}))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|