"""Verify AN publication bytes in Git's index or one immutable commit. No working-tree payload fallback, Git writes, network, bulk verification or scientific execution. The publication receipt SHA must be externally trusted. Only the 21 registered engineering sources are allowed in the AN model scope. """ from __future__ import annotations import argparse import hashlib import json import os from pathlib import Path, PurePosixPath import re import subprocess import sys PROJECT = '/data/digital-drosophila' MODEL = 'models/male-cns-lif/olfactory-conditioning-lab-v1' EXPERIMENT = 'experiments/phase4an-engineering-validation' REGISTRY = 'registry/phase4an-engineering-validation' MODULES = ('runtime_light', 'runtime_continuation', 'engineering_inputs', 'engineering_plan', 'engineering_compare', 'engineering_queue_audit', 'engineering_prepare', 'engineering_workflow', 'engineering_scopes', 'engineering_analysis') SOURCES = tuple(sorted([m + '.py' for m in MODULES] + ['test_' + m + '.py' for m in MODULES] + ['ENGINEERING_GATE_DRAFT.md'])) MAX_BYTES = 4 * 1024**2 SUFFIXES = frozenset(('.json', '.csv', '.md', '.log', '.sha256')) CONTROL = frozenset(('manifest.json', 'checksums.sha256')) SIDE = frozenset(('evidence-index.json', 'source-identity.json', 'seal-verification.json')) SHA = re.compile(r'[0-9a-f]{64}\Z') OID = re.compile(r'(?:[0-9a-f]{40}|[0-9a-f]{64})\Z') STATUSES = frozenset(('PASS-EXACT-LIGHT-CONTINUATION-ENGINEERING-NOT-LEARNING', 'FAIL-NUMERICAL', 'INCOMPLETE-COVERAGE')) def require(value, message): if not value: raise ValueError(message) def sha(data): return hashlib.sha256(data).hexdigest() def relative(name): require(type(name) is str and name and not any(c in name for c in '\\\x00\r\n\t'), 'Invalid relative path') path = PurePosixPath(name) require(not path.is_absolute() and str(path) == name and all(p not in ('', '.', '..') and ':' not in p for p in path.parts), 'Noncanonical or escaping path: ' + name) return name def parse(data): def pairs(rows): result = {} for name, value in rows: require(name not in result, 'Duplicate JSON field: ' + name) result[name] = value return result return json.loads(data.decode('utf-8'), object_pairs_hook=pairs, parse_constant=lambda value: (_ for _ in ()).throw(ValueError('Nonfinite JSON: ' + value))) def inventory(rows): require(type(rows) is list and rows, 'Missing payload inventory') indexed = {} for row in rows: require(type(row) is dict and set(row) == {'path', 'bytes', 'sha256'}, 'Invalid inventory row schema') path = relative(row['path']) require(path not in indexed and '__pycache__' not in PurePosixPath(path).parts and type(row['bytes']) is int and row['bytes'] >= 0 and type(row['sha256']) is str and SHA.fullmatch(row['sha256']), 'Invalid/duplicate inventory row: ' + path) indexed[path] = row return indexed class GitBlobs: def __init__(self, repo, staged, commit): require(type(staged) is bool and staged != (commit is not None), 'Select exactly one of staged or commit') self.repo = Path(repo).resolve(strict=True) env = os.environ.copy() for key in ('GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_COMMON_DIR', 'GIT_OBJECT_DIRECTORY', 'GIT_ALTERNATE_OBJECT_DIRECTORIES'): env.pop(key, None) env.update(GIT_OPTIONAL_LOCKS='0', GIT_NO_REPLACE_OBJECTS='1', GIT_NO_LAZY_FETCH='1') self.env = env self.base = ['git', '--no-optional-locks', '-c', 'core.fsmonitor=false', '-c', 'gc.auto=0', '-C', str(self.repo)] require(Path(self.command('rev-parse', '--show-toplevel').decode().strip()).resolve() == self.repo, 'Supply repository top-level') self.staged, self.commit = staged, None if not staged: require(type(commit) is str and commit and not commit.startswith('-') and not any(c in commit for c in '\x00\r\n'), 'Invalid commit selector') self.commit = self.command('rev-parse', '--verify', '--end-of-options', commit + '^{commit}').decode('ascii').strip() require(OID.fullmatch(self.commit), 'Unresolved commit identity') self.snapshot = self.listing() self.entries, self.verified = {}, {} require(self.snapshot.endswith(b'\0'), 'Empty or malformed scoped Git inventory') for raw in self.snapshot[:-1].split(b'\0'): metadata, path = raw.split(b'\t', 1); fields = metadata.decode('ascii').split(' ') require(len(fields) == 3, 'Malformed Git inventory entry') mode, second, third = fields oid = second if staged else third require((third == '0' if staged else second == 'blob') and mode in ('100644', '100755') and OID.fullmatch(oid), 'Unmerged, symlink, gitlink or nonregular blob is forbidden') name = relative(path.decode('utf-8')) require(name.startswith(MODEL + '/') or name.startswith(REGISTRY + '/'), 'Out-of-scope Git blob') require(name not in self.entries, 'Duplicate Git path') self.entries[name] = (mode, oid) self.batch = subprocess.Popen(self.base + ['cat-file', '--batch'], env=self.env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) def command(self, *args): result = subprocess.run(self.base + list(args), env=self.env, stdout=subprocess.PIPE, stderr=subprocess.PIPE) require(result.returncode == 0, 'Git read failed: ' + result.stderr.decode('utf-8', 'replace').strip()) return result.stdout def listing(self): return (self.command('ls-files', '--stage', '--full-name', '-z', '--', MODEL, REGISTRY) if self.staged else self.command('ls-tree', '-r', '-z', '--full-tree', self.commit, '--', MODEL, REGISTRY)) def read(self, path, expected=None): require(path in self.entries, 'Missing Git blob: ' + path) mode, oid = self.entries[path] self.batch.stdin.write((oid + '\n').encode('ascii')); self.batch.stdin.flush() fields = self.batch.stdout.readline(256).decode('ascii').strip().split(' ') require(len(fields) == 3 and fields[:2] == [oid, 'blob'] and fields[2].isdigit(), 'Invalid Git blob response') size = int(fields[2]); require(size <= MAX_BYTES, 'Selected Git blob exceeds 4 MiB: ' + path) data = self.batch.stdout.read(size) require(len(data) == size and self.batch.stdout.read(1) == b'\n', 'Truncated Git object') if expected is not None: require(size == expected['bytes'] and sha(data) == expected['sha256'], 'Git blob SHA/bytes mismatch: ' + path) self.verified[path] = dict(path=path, bytes=size, sha256=sha(data), git_oid=oid, git_mode=mode) return data def close(self): # On a malformed/oversize object the child might still be writing. # Terminating this read-only batch process cannot alter the repository. if self.batch.poll() is None: self.batch.terminate() self.batch.communicate(timeout=10) def _seal(manifest_bytes, checksum_bytes, receipt): require(sha(manifest_bytes) == receipt['experiment_manifest_sha256'] and sha(checksum_bytes) == receipt['experiment_checksums_sha256'], 'Receipt-bound seal control mismatch') manifest = parse(manifest_bytes) require(type(manifest) is dict and set(manifest) == {'root', 'files'} and manifest['root'] == PROJECT + '/' + EXPERIMENT, 'Wrong experiment seal root/schema') rows = inventory(manifest['files']); require(not (set(rows) & CONTROL), 'Seal controls cannot be payload rows') checks = {} for line in checksum_bytes.decode('utf-8').splitlines(): require(len(line) >= 67 and SHA.fullmatch(line[:64]) and line[64:66] == ' ', 'Malformed checksum row') name = relative(line[66:]); require(name not in checks, 'Duplicate checksum row'); checks[name] = line[:64] require(checks == {**{k: v['sha256'] for k, v in rows.items()}, 'manifest.json': sha(manifest_bytes)}, 'Checksum inventory or manifest self-SHA mismatch') return rows def _selection(row): path = PurePosixPath(row['path']) if path.parts[0] == 'extension-build': return None, 'build_artifact' if row['bytes'] > MAX_BYTES: return None, 'exceeds_4_MiB' if path.parts[:2] == ('protocol', 'source-snapshot') and len(path.parts) == 3 and path.name in SOURCES: return 'source-snapshot/' + path.name, None if path.suffix.lower() not in SUFFIXES: return None, 'bulk_binary_or_unselected_suffix_including_jsonl' return 'evidence/' + row['path'], None def verify(repo, receipt_sha, *, staged=False, commit=None): require(type(receipt_sha) is str and SHA.fullmatch(receipt_sha), 'Externally trusted receipt SHA-256 is mandatory') git = GitBlobs(repo, staged, commit) try: raw = git.read(REGISTRY + '/publication-receipt.json') require(sha(raw) == receipt_sha, 'External receipt SHA-256 mismatch') receipt = parse(raw) require(receipt.get('schema') == 'phase4an-engineering-publication-v1' and receipt.get('publication_complete') is True and receipt.get('registry') == REGISTRY and receipt.get('project_root') == PROJECT and receipt.get('bulk_remains_at') == EXPERIMENT and receipt.get('declared_status') in STATUSES and receipt.get('model_directory_sealed') is False and type(receipt.get('used_source_count')) is int and receipt['used_source_count'] == 21 and receipt.get('model_files_copied') is False and receipt.get('complete_bulk_backup') is False and receipt.get('training') is False and receipt.get('candidate_generated') is False and receipt.get('learning_pass_claimed') is False, 'Wrong AN publication scope or declarations') rows = inventory(receipt['files']); require('publication-receipt.json' not in rows, 'Receipt cannot self-hash') actual = {p[len(REGISTRY) + 1:] for p in git.entries if p.startswith(REGISTRY + '/')} require(actual == set(rows) | {'publication-receipt.json'}, 'Registry has unlisted/missing Git files') required = SIDE | {'evidence/manifest.json', 'evidence/checksums.sha256', 'evidence/protocol/preregistered.json', 'evidence/audit/final-status.json', 'evidence/audit/engineering-analysis.json'} require(required <= rows.keys(), 'Missing registry controls/evidence') retained = {} for name, row in rows.items(): require(row['bytes'] <= MAX_BYTES, 'Registry file exceeds publication cap') data = git.read(REGISTRY + '/' + name, row) if name in required: retained[name] = data payload = _seal(retained['evidence/manifest.json'], retained['evidence/checksums.sha256'], receipt) require({name for name in payload if name.startswith('protocol/source-snapshot/')} == {'protocol/source-snapshot/' + name for name in SOURCES}, 'Sealed source snapshots are not exactly the 21 used sources') controls = {name: dict(path=name, bytes=len(retained['evidence/' + name]), sha256=sha(retained['evidence/' + name])) for name in CONTROL} expected_index, omitted = {}, [] for row in [*payload.values(), *controls.values()]: destination, reason = _selection(row) if destination is None: omitted.append(dict(row, reason=reason)) else: require(destination not in expected_index, 'Duplicate publication destination') expected_index[destination] = dict(source=EXPERIMENT + '/' + row['path'], copy=destination, bytes=row['bytes'], sha256=row['sha256']) indexed = {} index = parse(retained['evidence-index.json']); require(type(index) is list, 'Malformed evidence index') for row in index: require(type(row) is dict and set(row) == {'source', 'copy', 'bytes', 'sha256'}, 'Invalid evidence-index row') require(type(row['bytes']) is int and row['bytes'] >= 0 and type(row['sha256']) is str and SHA.fullmatch(row['sha256']), 'Invalid evidence-index size/hash') name = relative(row['copy']); relative(row['source']); require(name not in indexed, 'Duplicate evidence-index destination') indexed[name] = row require(indexed == expected_index and set(rows) == set(indexed) | SIDE, 'Evidence index does not match seal/selection/receipt') for name, row in indexed.items(): require(rows[name] == dict(path=name, bytes=row['bytes'], sha256=row['sha256']), 'Evidence receipt row mismatch') require(type(receipt.get('copied_evidence_files')) is int and receipt['copied_evidence_files'] == len(indexed) and type(receipt.get('copied_evidence_bytes')) is int and receipt['copied_evidence_bytes'] == sum(r['bytes'] for r in indexed.values()) and sorted(receipt.get('experiment_payload_not_copied', []), key=lambda r: r['path']) == sorted(omitted, key=lambda r: r['path']), 'Copied/omitted payload accounting mismatch') proof = parse(retained['seal-verification.json']) require(proof.get('pass_all') is True and proof.get('root') == PROJECT + '/' + EXPERIMENT and proof.get('manifest_sha256') == receipt['experiment_manifest_sha256'] and proof.get('checksums_sha256') == receipt['experiment_checksums_sha256'] and proof.get('payload_file_count') == len(payload) and proof.get('payload_bytes') == sum(r['bytes'] for r in payload.values()), 'Seal-verification sidecar contradicts controls') reg = parse(retained['evidence/protocol/preregistered.json']) require(reg.get('schema') == 'phase4an-engineering-registration-v1' and reg.get('training') is False and sha(retained['evidence/protocol/preregistered.json']) == receipt['preregistration_sha256'], 'Registration anchor mismatch') identities = parse(retained['source-identity.json']) require(type(identities) is list and len(identities) == 21 and receipt.get('source_identity') == 'source-identity.json', 'Missing source identity inventory') require(set(reg['source_sha256']) == {PROJECT + '/' + MODEL + '/' + name for name in SOURCES}, 'Registration does not bind exactly 21 engineering sources') observed_model = {p[len(MODEL) + 1:] for p in git.entries if p.startswith(MODEL + '/')} require(observed_model == set(SOURCES), 'Model Git scope must contain exactly the 21 registered used sources, not drafts') by_copy = {} for row in identities: require(type(row) is dict and set(row) == {'model_source', 'sealed_source', 'copy', 'bytes', 'sha256'}, 'Invalid source identity row') require(type(row['bytes']) is int and row['bytes'] >= 0 and type(row['sha256']) is str and SHA.fullmatch(row['sha256']), 'Invalid source identity size/hash') name = relative(row['copy']); require(name not in by_copy, 'Duplicate source identity'); by_copy[name] = row require(set(by_copy) == {'source-snapshot/' + name for name in SOURCES}, 'Source identity coverage mismatch') for name in SOURCES: copy_name, sealed = 'source-snapshot/' + name, 'protocol/source-snapshot/' + name row, manifest_row = by_copy[copy_name], payload[sealed] expected = dict(model_source=MODEL + '/' + name, sealed_source=EXPERIMENT + '/' + sealed, copy=copy_name, bytes=manifest_row['bytes'], sha256=manifest_row['sha256']) require(row == expected and rows[copy_name] == dict(path=copy_name, bytes=row['bytes'], sha256=row['sha256']) and reg['source_sha256'][PROJECT + '/' + MODEL + '/' + name] == row['sha256'] and reg['input_sha256'].get(PROJECT + '/' + EXPERIMENT + '/' + sealed) == row['sha256'], 'Source/snapshot/registration disagreement: ' + name) git.read(MODEL + '/' + name, row) for name, key in [('audit/final-status.json', 'final_sha256'), ('audit/engineering-analysis.json', 'analysis_sha256')]: require(sha(retained['evidence/' + name]) == receipt[key], 'Final/analysis anchor mismatch') require(parse(retained['evidence/' + name]).get('status') == receipt['declared_status'], 'Declared status disagreement') require(git.listing() == git.snapshot, 'Scoped Git inventory changed during verification') return dict(pass_all=True, verification='AN actual Git blob publication integrity', source='staged-index' if staged else 'commit', commit=git.commit, receipt_sha256=receipt_sha, declared_status=receipt['declared_status'], model_source_files=21, registry_files=len(rows) + 1, verified_blob_count=len(git.verified), verified_bytes=sum(v['bytes'] for v in git.verified.values()), scoped_git_inventory_sha256=sha(git.snapshot), working_tree_payload_used=False, git_index_modified=False, experiment_bulk_verified_by_this_tool=False, scientific_gate_executed=False, model_directory_declared_sealed=False, publication_integrity_is_not_scientific_pass=True) finally: git.close() def main(argv=None): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--repo', required=True) parser.add_argument('--receipt-sha256', required=True) source = parser.add_mutually_exclusive_group(required=True) source.add_argument('--staged', action='store_true'); source.add_argument('--commit') args = parser.parse_args(argv) try: result = verify(args.repo, args.receipt_sha256, staged=args.staged, commit=args.commit) except (ValueError, KeyError, TypeError, AttributeError, OSError, UnicodeError, EOFError) as error: print(json.dumps(dict(pass_all=False, error_type=type(error).__name__, error=str(error), scientific_gate_executed=False))) return 1 print(json.dumps(result, ensure_ascii=False, allow_nan=False)) return 0 if __name__ == '__main__': sys.exit(main())