Seal Phase 4AM whole-brain plastic interface audit without learning claim
This commit is contained in:
@@ -0,0 +1,340 @@
|
||||
"""Fetch only externally anchored Phase 4AM small evidence and model source.
|
||||
|
||||
No SSH connection is made at import. The API accepts an already authenticated
|
||||
SFTP client; the CLI rejects unknown host keys. This is not a trajectory backup
|
||||
or a local re-verification of remote bulk payloads/scientific gates.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path, PurePosixPath
|
||||
import re
|
||||
import stat
|
||||
|
||||
REMOTE_ROOT = '/data/digital-drosophila'
|
||||
MODEL_RELATIVE = 'models/male-cns-lif/wholebrain-plastic-interface-v1'
|
||||
EXPERIMENT_RELATIVE = 'experiments/phase4am-wholebrain-plastic-interface'
|
||||
REGISTRY_RELATIVE = 'registry/phase4am-wholebrain-plastic-interface'
|
||||
MODEL_ROOT = REMOTE_ROOT + '/' + MODEL_RELATIVE
|
||||
REGISTRY_ROOT = REMOTE_ROOT + '/' + REGISTRY_RELATIVE
|
||||
DEFAULT_DESTINATION = Path(__file__).resolve().parents[2] / 'reports/phase4am-wholebrain-plastic-interface'
|
||||
MAX_FILE_BYTES = 4 * 1024**2
|
||||
MAX_TOTAL_BYTES = 1024**3
|
||||
EVIDENCE_SUFFIXES = frozenset(('.json', '.csv', '.md', '.log', '.sha256'))
|
||||
SOURCE_SUFFIXES = frozenset(('.py', '.json', '.md', '.yaml', '.yml', '.toml', '.txt', '.sha256', '.cpp', '.cu', '.h', '.hpp'))
|
||||
CONTROLS = ('manifest.json', 'checksums.sha256')
|
||||
GENERATED = ('seal-verification.json', 'evidence-index.json')
|
||||
SHA = re.compile(r'[0-9a-f]{64}\Z')
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def sha(data):
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def unique_object(pairs):
|
||||
result = {}
|
||||
for key, value in pairs:
|
||||
require(key not in result, 'Duplicate JSON key: ' + key)
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
|
||||
def json_bytes(data):
|
||||
def bad(value):
|
||||
raise ValueError('Nonfinite JSON constant: ' + value)
|
||||
return json.loads(data.decode('utf-8'), object_pairs_hook=unique_object, parse_constant=bad)
|
||||
|
||||
|
||||
def relative_name(value):
|
||||
require(isinstance(value, str) and value and not any(c in value for c in ('\\', '\0', '\r', '\n', ':')),
|
||||
'Invalid relative filename')
|
||||
path = PurePosixPath(value)
|
||||
require(not path.is_absolute() and path.as_posix() == value and
|
||||
all(part not in ('', '.', '..', '__pycache__') and not part.endswith((' ', '.')) for part in path.parts),
|
||||
'Noncanonical/escaping/cache filename: ' + value)
|
||||
devices = {'CON', 'PRN', 'AUX', 'NUL', *(f'COM{i}' for i in range(1, 10)), *(f'LPT{i}' for i in range(1, 10))}
|
||||
require(all(part.split('.')[0].upper() not in devices for part in path.parts), 'Reserved local filename')
|
||||
return path
|
||||
|
||||
|
||||
def file_row(row, *, small=False):
|
||||
require(isinstance(row, dict) and set(row) == {'path', 'bytes', 'sha256'}, 'Invalid hash-inventory row')
|
||||
relative_name(row['path'])
|
||||
require(type(row['bytes']) is int and row['bytes'] >= 0 and isinstance(row['sha256'], str) and
|
||||
SHA.fullmatch(row['sha256']), 'Invalid file size/SHA')
|
||||
if small:
|
||||
require(row['bytes'] <= MAX_FILE_BYTES, 'Receipt file exceeds 4 MiB: ' + row['path'])
|
||||
return row
|
||||
|
||||
|
||||
class BoundedSFTP:
|
||||
def __init__(self, sftp):
|
||||
self.sftp = sftp
|
||||
self.directories = set()
|
||||
|
||||
def read(self, remote, size=None, checksum=None):
|
||||
require(remote.startswith(REGISTRY_ROOT + '/') or remote.startswith(MODEL_ROOT + '/'),
|
||||
'Remote request outside fixed AM registry/model scope')
|
||||
# Never list a remote directory or discover unregistered sidecars.
|
||||
parent = PurePosixPath(remote).parent
|
||||
for directory in reversed((parent, *parent.parents)):
|
||||
name = str(directory)
|
||||
if name not in self.directories:
|
||||
require(stat.S_ISDIR(self.sftp.lstat(name).st_mode), 'Remote symlink/non-directory: ' + name)
|
||||
self.directories.add(name)
|
||||
info = self.sftp.lstat(remote)
|
||||
require(stat.S_ISREG(info.st_mode), 'Remote symlink/nonregular file: ' + remote)
|
||||
require(0 <= info.st_size <= MAX_FILE_BYTES and (size is None or info.st_size == size),
|
||||
'Remote size outside expected small-file limit: ' + remote)
|
||||
chunks, count = [], 0
|
||||
with self.sftp.open(remote, 'rb') as source:
|
||||
while True:
|
||||
block = source.read(min(1 << 20, MAX_FILE_BYTES + 1 - count))
|
||||
if not block:
|
||||
break
|
||||
count += len(block)
|
||||
require(count <= MAX_FILE_BYTES, 'Remote file grew beyond download limit')
|
||||
chunks.append(block)
|
||||
data = b''.join(chunks)
|
||||
require(len(data) == info.st_size and (size is None or len(data) == size), 'Remote size changed during read')
|
||||
if checksum is not None:
|
||||
require(sha(data) == checksum, 'Remote file SHA mismatch: ' + remote)
|
||||
return data
|
||||
|
||||
|
||||
def validate_receipt(data, expected_sha):
|
||||
require(sha(data) == expected_sha, 'External publication-receipt SHA mismatch')
|
||||
receipt = json_bytes(data)
|
||||
require(isinstance(receipt, dict) and receipt.get('publication_complete') is True and
|
||||
receipt.get('registry') == REGISTRY_RELATIVE and receipt.get('project_root') == REMOTE_ROOT,
|
||||
'Wrong/incomplete AM publication receipt')
|
||||
for key in ('source_trees_verified_before_and_after_copy', 'publication_success_is_not_scientific_pass'):
|
||||
require(receipt.get(key) is True, 'Missing publication qualification: ' + key)
|
||||
for key in ('complete_bulk_backup', 'scientific_gate_executed_by_publisher',
|
||||
'scientific_gate_certified_by_publication', 'candidate_generated', 'learning_achieved'):
|
||||
require(receipt.get(key) is False, 'Unexpected publication claim: ' + key)
|
||||
require(receipt.get('limits') == dict(max_selected_evidence_file_bytes=MAX_FILE_BYTES,
|
||||
selected_suffixes=sorted(EVIDENCE_SUFFIXES)), 'Unexpected publication selection limits')
|
||||
for key in ('model_manifest_sha256', 'experiment_manifest_sha256'):
|
||||
require(isinstance(receipt.get(key), str) and SHA.fullmatch(receipt[key]), 'Missing manifest anchor: ' + key)
|
||||
rows = {}
|
||||
require(isinstance(receipt.get('files'), list) and receipt['files'], 'Empty receipt inventory')
|
||||
for row in receipt['files']:
|
||||
file_row(row, small=True)
|
||||
name = row['path']
|
||||
require(name not in rows, 'Duplicate receipt file: ' + name)
|
||||
allowed = name in GENERATED or name in ('model/manifest.json', 'model/checksums.sha256') or (
|
||||
name.startswith('evidence/') and PurePosixPath(name).suffix.lower() in EVIDENCE_SUFFIXES)
|
||||
require(allowed, 'Receipt attempts a non-evidence/sidecar download: ' + name)
|
||||
rows[name] = row
|
||||
required = {'model/manifest.json', 'model/checksums.sha256', 'evidence/manifest.json',
|
||||
'evidence/checksums.sha256', 'evidence/audit/final-status.json',
|
||||
'evidence/regression/old-integrity-after.json', 'evidence/audit/scientific-summary.json',
|
||||
'evidence/protocol/preregistered.json', 'evidence/audit/zero-complete.json',
|
||||
'evidence/audit/static25-complete.json', 'evidence/regression/frozen-five.json', *GENERATED}
|
||||
require(required.issubset(rows), 'Receipt omits required AM evidence/seal controls')
|
||||
return receipt, rows
|
||||
|
||||
|
||||
def validate_manifest(manifest_data, checksums_data, expected_root, expected_sha):
|
||||
require(sha(manifest_data) == expected_sha, 'Receipt/manifest SHA mismatch')
|
||||
manifest = json_bytes(manifest_data)
|
||||
require(isinstance(manifest, dict) and set(manifest) == {'root', 'files'} and
|
||||
manifest['root'] == expected_root and isinstance(manifest['files'], list) and manifest['files'],
|
||||
'Wrong seal manifest schema/root')
|
||||
rows = {}
|
||||
for row in manifest['files']:
|
||||
file_row(row)
|
||||
require(row['path'] not in rows and row['path'] not in CONTROLS, 'Duplicate/control manifest payload')
|
||||
rows[row['path']] = row
|
||||
checksums = {}
|
||||
for line in checksums_data.decode('utf-8').splitlines():
|
||||
require(len(line) >= 67 and line[64:66] == ' ' and SHA.fullmatch(line[:64]), 'Invalid checksum line')
|
||||
name = line[66:]
|
||||
relative_name(name)
|
||||
require(name not in checksums, 'Duplicate checksum filename')
|
||||
checksums[name] = line[:64]
|
||||
require(checksums == {**{name: row['sha256'] for name, row in rows.items()}, 'manifest.json': expected_sha},
|
||||
'Manifest self-SHA/checksum inventory mismatch')
|
||||
return rows
|
||||
|
||||
|
||||
def local_name(registry_name):
|
||||
if registry_name.startswith('evidence/'):
|
||||
return registry_name[len('evidence/'):]
|
||||
if registry_name.startswith('model/'):
|
||||
return registry_name
|
||||
return 'registry/' + registry_name
|
||||
|
||||
|
||||
def _check_local_target(destination):
|
||||
require(destination.is_absolute(), 'Report destination must be absolute')
|
||||
for part in (destination, *destination.parents):
|
||||
require(not part.is_symlink() and not getattr(part, 'is_junction', lambda: False)(),
|
||||
'Local symlink/junction destination is forbidden')
|
||||
require(not destination.exists(), 'Report destination exists; no overwrite/resume')
|
||||
|
||||
|
||||
def _write(path, data):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open('xb') as output:
|
||||
output.write(data)
|
||||
require(path.stat().st_size == len(data) and sha(path.read_bytes()) == sha(data), 'Local write verification failed')
|
||||
|
||||
|
||||
def fetch(sftp, destination, receipt_sha256, *, max_total_bytes=MAX_TOTAL_BYTES):
|
||||
"""SFTP read-only API. Destination is exclusive; receipt SHA is out-of-band.
|
||||
|
||||
Registry evidence/* mirrors into the report root, model/* into model/, and
|
||||
receipt/generated publication metadata into registry/. Only source-whitelisted
|
||||
model payloads are added; every transferred file is individually SHA checked.
|
||||
"""
|
||||
require(isinstance(receipt_sha256, str) and SHA.fullmatch(receipt_sha256.lower()), 'External receipt SHA256 is required')
|
||||
receipt_sha256 = receipt_sha256.lower()
|
||||
require(type(max_total_bytes) is int and max_total_bytes > 0, 'Invalid total download budget')
|
||||
destination = Path(destination)
|
||||
_check_local_target(destination)
|
||||
remote = BoundedSFTP(sftp)
|
||||
receipt_bytes = remote.read(REGISTRY_ROOT + '/publication-receipt.json', checksum=receipt_sha256)
|
||||
receipt, rows = validate_receipt(receipt_bytes, receipt_sha256)
|
||||
cached = {}
|
||||
for prefix in ('model/', 'evidence/'):
|
||||
for control in CONTROLS:
|
||||
name = prefix + control; row = rows[name]
|
||||
cached[name] = remote.read(REGISTRY_ROOT + '/' + name, row['bytes'], row['sha256'])
|
||||
model_rows = validate_manifest(cached['model/manifest.json'], cached['model/checksums.sha256'],
|
||||
MODEL_ROOT, receipt['model_manifest_sha256'])
|
||||
experiment_rows = validate_manifest(cached['evidence/manifest.json'], cached['evidence/checksums.sha256'],
|
||||
REMOTE_ROOT + '/' + EXPERIMENT_RELATIVE, receipt['experiment_manifest_sha256'])
|
||||
# Model source is fetched from its sealed model path, not the registry. Bind
|
||||
# its current controls to the receipt before reading any source payload.
|
||||
for control in CONTROLS:
|
||||
row = rows['model/' + control]
|
||||
remote.read(MODEL_ROOT + '/' + control, row['bytes'], row['sha256'])
|
||||
for name, row in rows.items():
|
||||
if name.startswith('evidence/') and name[len('evidence/'):] not in CONTROLS:
|
||||
original = experiment_rows.get(name[len('evidence/'):])
|
||||
require(original is not None and all(row[key] == original[key] for key in ('bytes', 'sha256')),
|
||||
'Registry evidence disagrees with experiment seal: ' + name)
|
||||
for name in GENERATED:
|
||||
row = rows[name]
|
||||
cached[name] = remote.read(REGISTRY_ROOT + '/' + name, row['bytes'], row['sha256'])
|
||||
proof = json_bytes(cached['seal-verification.json'])
|
||||
for key, root, manifest_rows, prefix in (('model', MODEL_ROOT, model_rows, 'model/'),
|
||||
('experiment', REMOTE_ROOT + '/' + EXPERIMENT_RELATIVE, experiment_rows, 'evidence/')):
|
||||
item = proof.get(key, {})
|
||||
require(item.get('pass_all') is True and item.get('root') == root and
|
||||
item.get('manifest_sha256') == receipt[key + '_manifest_sha256'] and
|
||||
item.get('checksums_sha256') == rows[prefix + 'checksums.sha256']['sha256'] and
|
||||
item.get('payload_file_count') == len(manifest_rows) and
|
||||
item.get('payload_bytes') == sum(row['bytes'] for row in manifest_rows.values()),
|
||||
'Publication seal-verification disagrees with manifest: ' + key)
|
||||
index = json_bytes(cached['evidence-index.json'])
|
||||
require(isinstance(index, list), 'Invalid publication evidence index')
|
||||
indexed = set()
|
||||
for item in index:
|
||||
require(isinstance(item, dict) and set(item) == {'source', 'copy', 'bytes', 'sha256'}, 'Invalid evidence-index row')
|
||||
name = item['copy']
|
||||
require(name in rows and name not in GENERATED and name not in indexed, 'Duplicate/unexpected evidence-index copy')
|
||||
indexed.add(name)
|
||||
expected_source = (EXPERIMENT_RELATIVE + '/' + name[len('evidence/'):] if name.startswith('evidence/')
|
||||
else MODEL_RELATIVE + '/' + name[len('model/'):])
|
||||
require(item['source'] == expected_source and all(item[key] == rows[name][key] for key in ('bytes', 'sha256')),
|
||||
'Evidence-index source/hash mismatch')
|
||||
require(indexed == set(rows) - set(GENERATED), 'Evidence-index coverage mismatch')
|
||||
plan = [(REGISTRY_ROOT + '/' + name, local_name(name), row, cached.get(name)) for name, row in sorted(rows.items())]
|
||||
omitted = []
|
||||
for name, row in sorted(model_rows.items()):
|
||||
if PurePosixPath(name).suffix.lower() in SOURCE_SUFFIXES and row['bytes'] <= MAX_FILE_BYTES:
|
||||
plan.append((MODEL_ROOT + '/' + name, 'model/' + name, row, None))
|
||||
else:
|
||||
omitted.append(dict(row, reason='model_non_source_or_over_4_MiB'))
|
||||
paths = ['registry/publication-receipt.json', 'fetch-verification.json', 'fetch-failure.json'] + [entry[1] for entry in plan]
|
||||
lowered = [relative_name(name).as_posix().casefold() for name in paths]
|
||||
require(len(set(lowered)) == len(lowered) and not any(
|
||||
parent.as_posix().casefold() in set(lowered) for name in paths for parent in PurePosixPath(name).parents if str(parent) != '.'),
|
||||
'Local case-insensitive/path-prefix collision')
|
||||
planned_bytes = len(receipt_bytes) + sum(entry[2]['bytes'] for entry in plan)
|
||||
require(planned_bytes <= max_total_bytes, 'Selected evidence exceeds total download budget')
|
||||
_check_local_target(destination)
|
||||
destination.mkdir(parents=True, exist_ok=False)
|
||||
try:
|
||||
_write(destination / 'registry/publication-receipt.json', receipt_bytes)
|
||||
downloaded = []
|
||||
for source, local, row, data in plan:
|
||||
if data is None:
|
||||
data = remote.read(source, row['bytes'], row['sha256'])
|
||||
require(len(data) == row['bytes'] and sha(data) == row['sha256'], 'Cached/downloaded file hash mismatch')
|
||||
_write(destination / local, data)
|
||||
downloaded.append(dict(remote=source, local=local, bytes=len(data), sha256=sha(data)))
|
||||
final = json_bytes((destination / 'audit/final-status.json').read_bytes())
|
||||
require(final.get('status') == receipt.get('declared_audit_status'), 'Receipt/final-status declaration mismatch')
|
||||
for key, value in receipt.get('declared_gate_results', {}).items():
|
||||
require(final.get(key) == value and type(final.get(key)) is type(value), 'Receipt/final gate flag mismatch')
|
||||
for key, value in receipt.get('declared_audit_counts', {}).items():
|
||||
require(final.get(key) == value and type(final.get(key)) is type(value), 'Receipt/final coverage mismatch')
|
||||
# Re-read immutable anchors, not unselected experiment payloads.
|
||||
remote.read(REGISTRY_ROOT + '/publication-receipt.json', len(receipt_bytes), receipt_sha256)
|
||||
for control in CONTROLS:
|
||||
row = rows['model/' + control]
|
||||
remote.read(MODEL_ROOT + '/' + control, row['bytes'], row['sha256'])
|
||||
result = dict(fetch_complete=True, publication_receipt_sha256=receipt_sha256,
|
||||
external_receipt_anchor_required=True, declared_audit_status=final['status'],
|
||||
model_manifest_sha256=receipt['model_manifest_sha256'], experiment_manifest_sha256=receipt['experiment_manifest_sha256'],
|
||||
both_manifest_self_checksums_verified=True, every_downloaded_file_sha256_verified=True,
|
||||
files=downloaded, file_count=len(downloaded), selected_bytes=planned_bytes,
|
||||
model_payload_not_downloaded=omitted, all_remote_experiment_bulk_rehashed_locally=False,
|
||||
full_remote_seal_verification_is_publisher_receipt_claim=True, complete_bulk_backup=False,
|
||||
scientific_gate_executed=False, scientific_pass_certified_by_fetch=False,
|
||||
fetch_success_is_not_scientific_pass=True, candidate_generated=False, learning_achieved=False,
|
||||
unregistered_sidecar_requested=False, existing_files_modified=False)
|
||||
_write(destination / 'fetch-verification.json', (json.dumps(result, ensure_ascii=False, indent=2, allow_nan=False) + '\n').encode())
|
||||
return result
|
||||
except Exception as error:
|
||||
failure = destination / 'fetch-failure.json'
|
||||
if not failure.exists():
|
||||
_write(failure, (json.dumps(dict(fetch_complete=False, type=type(error).__name__, message=str(error),
|
||||
partial_outputs_preserved=True, existing_files_modified=False), allow_nan=False) + '\n').encode())
|
||||
raise
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--receipt-sha256', required=True, help='Out-of-band SHA printed by the verified AM publisher')
|
||||
parser.add_argument('--destination', type=Path, default=DEFAULT_DESTINATION)
|
||||
parser.add_argument('--host', default='192.168.10.51')
|
||||
parser.add_argument('--user', default='qishizhe')
|
||||
parser.add_argument('--port', type=int, default=22)
|
||||
parser.add_argument('--password-env', default='MALECNS_REMOTE_PASSWORD')
|
||||
parser.add_argument('--known-hosts', type=Path, default=Path.home() / '.ssh/known_hosts',
|
||||
help='Explicit existing user known_hosts; unknown hosts are always rejected')
|
||||
args = parser.parse_args()
|
||||
require(SHA.fullmatch(args.receipt_sha256.lower()), 'External receipt SHA256 is required')
|
||||
_check_local_target(args.destination)
|
||||
require(args.known_hosts.is_file(), 'Known-hosts file is missing; configure trust externally, never auto-add')
|
||||
import paramiko
|
||||
client = paramiko.SSHClient()
|
||||
client.load_system_host_keys(str(args.known_hosts))
|
||||
client.set_missing_host_key_policy(paramiko.RejectPolicy())
|
||||
try:
|
||||
client.connect(args.host, port=args.port, username=args.user, password=os.environ.get(args.password_env),
|
||||
timeout=15, auth_timeout=15, banner_timeout=15)
|
||||
with client.open_sftp() as sftp:
|
||||
result = fetch(sftp, args.destination, args.receipt_sha256)
|
||||
print(json.dumps({key: result[key] for key in ('fetch_complete', 'publication_receipt_sha256',
|
||||
'declared_audit_status', 'file_count', 'fetch_success_is_not_scientific_pass')}), flush=True)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
"""Exclusive small-evidence publication after both Phase 4AM seals verify.
|
||||
|
||||
Publication verifies and copies bytes. It never runs a brain, re-evaluates a
|
||||
scientific gate, converts FAIL/INCOMPLETE into PASS, or changes frozen sources.
|
||||
The source-pinned Phase 4AI verification/copy helpers are executed unchanged.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path, PurePosixPath
|
||||
|
||||
|
||||
ROOT = Path('/data/digital-drosophila')
|
||||
MODEL_RELATIVE = Path('models/male-cns-lif/wholebrain-plastic-interface-v1')
|
||||
EXPERIMENT_RELATIVE = Path('experiments/phase4am-wholebrain-plastic-interface')
|
||||
REGISTRY_RELATIVE = Path('registry/phase4am-wholebrain-plastic-interface')
|
||||
HELPER_RELATIVE = Path('models/male-cns-lif/pn-recruitment-audit-v1/publication.py')
|
||||
HELPER_SHA256 = '7a79c499098b74163e7bb16adabcbc54e7069dafa204d0b99c7f4c03d79f8016'
|
||||
MAX_EVIDENCE_BYTES = 4 * 1024 ** 2
|
||||
EVIDENCE_SUFFIXES = frozenset(('.json', '.csv', '.md', '.log', '.sha256'))
|
||||
FINAL_STATUS = 'PASS-EXACT-WHOLEBRAIN-PLASTIC-INTERFACE-NOT-LEARNING'
|
||||
# Finalize currently seals only this completed diagnostic status. A partial
|
||||
# run is not promoted; future failure sealing needs an explicit new contract.
|
||||
FINAL_STATUSES = frozenset((FINAL_STATUS,))
|
||||
REQUIRED_EVIDENCE = ('audit/final-status.json', 'regression/old-integrity-after.json',
|
||||
'audit/scientific-summary.json', 'protocol/preregistered.json',
|
||||
'audit/zero-complete.json', 'audit/static25-complete.json')
|
||||
REFERENCE_EVIDENCE = 'regression/frozen-five.json'
|
||||
GATE_FIELDS = ('zero_overlay_exact', 'static25_repeat_exact', 'old_integrity_pass',
|
||||
'frozen_five_reference_pass', 'frozen_eligibility_and_mask_unchanged', 'base_weights_unchanged')
|
||||
COUNT_FIELDS = ('primary_trials_per_stage', 'repeats_per_stage')
|
||||
COHORTS = ('seed_validation', 'novel_to_aj')
|
||||
MODES = ('physiological', 'matched_energy')
|
||||
STAGES = ('zero', 'static25')
|
||||
POPULATIONS = ('KC', 'MBON', 'mask_recipient_MBON', 'DAN', 'MBON_downstream', 'descending', 'motor')
|
||||
REFERENCE_NAMES = ('auditory-10hz', 'auditory-100hz', 'olfactory-50hz',
|
||||
'mechanosensory-50hz', 'gustatory-50hz')
|
||||
EXPECTED_CONFIG = dict(phase='4AM',
|
||||
purpose='Exact D18 trial-boundary plastic-overlay interface and closed-loop transfer gate',
|
||||
neurons=166700, edges=25582938, candidate_edges=14232, primary_trials=120, technical_repeats=24,
|
||||
steps=1000, dt_ms=.1, apl_delay_steps=18, stages=list(STAGES), max_fractional_depression=.25,
|
||||
virtual_gate_tick=900, gpu_uuid='GPU-79f3a85d-adce-7833-7003-6488140ca596', output_budget_bytes=17179869184,
|
||||
training=False, candidate_generation=False, static_test_is_learning=False,
|
||||
precision_bridge='base float64 SI plus separate float64(delta float32 mV) times 0.001; zero rows untouched',
|
||||
future_learning='Original frozen eligibility-v2 and additive trial-boundary update; artificial scalar D remains separate from actual DAN events')
|
||||
SCIENCE_SCOPE = 'Valid interface and recording; reported static response is not an associative learning PASS'
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def load_helpers(path):
|
||||
"""Instantiate the unchanged module from its exact pinned source bytes.
|
||||
|
||||
Using module_from_spec plus the source bytes avoids writing/importing a pyc
|
||||
inside the sealed old model, even if the publisher was invoked without -B.
|
||||
No old module global is redirected or modified after module initialization.
|
||||
"""
|
||||
path = Path(path)
|
||||
require(path.is_absolute() and path.is_file() and not path.is_symlink(), 'Invalid frozen helper path')
|
||||
data = path.read_bytes()
|
||||
require(hashlib.sha256(data).hexdigest() == HELPER_SHA256, 'Frozen AI publication helper hash mismatch')
|
||||
spec = importlib.util.spec_from_file_location('phase4am_frozen_ai_publication_helpers', path)
|
||||
require(spec is not None and spec.loader is not None, 'Unable to define frozen helper module')
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
exec(compile(data, str(path), 'exec'), module.__dict__)
|
||||
require(module._digest(path) == HELPER_SHA256, 'Helper source changed while loading')
|
||||
return module
|
||||
|
||||
|
||||
def _copy(helper, source, destination, size, checksum):
|
||||
"""Separate local delegation point for interruption-only synthetic tests."""
|
||||
helper._copy_verified(source, destination, size, checksum)
|
||||
|
||||
|
||||
def _write_small_json(helper, path, value):
|
||||
data = (json.dumps(value, indent=2, ensure_ascii=False, allow_nan=False) + '\n').encode('utf-8')
|
||||
require(len(data) <= MAX_EVIDENCE_BYTES, 'Generated registry evidence exceeds 4 MiB: ' + str(path))
|
||||
with Path(path).open('xb') as stream:
|
||||
stream.write(data)
|
||||
require(Path(path).stat().st_size == len(data), 'Generated registry evidence size changed')
|
||||
|
||||
|
||||
def _small_json(helper, experiment, rows, relative):
|
||||
require(relative in rows, 'Required finalized evidence absent from seal: ' + relative)
|
||||
require(rows[relative]['bytes'] <= MAX_EVIDENCE_BYTES, 'Required finalized evidence exceeds 4 MiB')
|
||||
return helper._json_bytes((experiment / relative).read_bytes())
|
||||
|
||||
|
||||
def _planned_inventory(registration, config):
|
||||
require(registration.get('config') == config and registration.get('training') is False and
|
||||
registration.get('candidate_generated') is False and registration.get('before_new_brain_trajectories') is True,
|
||||
'Preregistration configuration/scope mismatch')
|
||||
planned = registration.get('planned')
|
||||
require(isinstance(planned, list) and len(planned) == 144, 'Planned inventory must contain 144 records')
|
||||
primary, repeated = {}, {}
|
||||
for row in planned:
|
||||
require(isinstance(row, dict) and isinstance(row.get('name'), str) and row['name'] and
|
||||
PurePosixPath(row['name']).name == row['name'] and row['name'] not in ('.', '..') and
|
||||
not any(c in row['name'] for c in ('\\', ':', '\0', '\r', '\n')), 'Invalid planned case name')
|
||||
require(row.get('cohort') in COHORTS and row.get('mode') in MODES and type(row.get('seed')) is int and
|
||||
isinstance(row.get('odor_id'), str) and row['odor_id'] and type(row.get('repeat')) is int and
|
||||
row['repeat'] in (0, 1), 'Invalid planned cohort/mode/seed/repeat')
|
||||
target = primary if row['repeat'] == 0 else repeated
|
||||
require(row['name'] not in target, 'Duplicate planned case/repeat')
|
||||
target[row['name']] = row
|
||||
require(len(primary) == 120 and len(repeated) == 24, 'Planned primary/repeat coverage mismatch')
|
||||
seeds = {row['seed'] for row in primary.values()}
|
||||
require(len(seeds) == 5, 'Planned inventory requires five seeds')
|
||||
identities = {(r['cohort'], r['mode'], r['odor_id'], r['seed']) for r in primary.values()}
|
||||
require(len(identities) == 120, 'Duplicate planned scientific identity')
|
||||
for cohort in COHORTS:
|
||||
odors_by_mode = []
|
||||
for mode in MODES:
|
||||
block = [r for r in primary.values() if r['cohort'] == cohort and r['mode'] == mode]
|
||||
odors = {r['odor_id'] for r in block}
|
||||
require(len(block) == 30 and len(odors) == 6 and
|
||||
{(r['odor_id'], r['seed']) for r in block} == {(o, s) for o in odors for s in seeds},
|
||||
'Incomplete planned cohort-mode inventory')
|
||||
odors_by_mode.append(odors)
|
||||
require(odors_by_mode[0] == odors_by_mode[1], 'Modes use different odor identities')
|
||||
require(len({r['odor_id'] for r in primary.values()}) == 12, 'Cohorts must retain twelve distinct frozen odors')
|
||||
expected_repeats = {name for name, r in primary.items() if r['seed'] == min(seeds)}
|
||||
require(set(repeated) == expected_repeats and all(repeated[name] == dict(primary[name], repeat=1)
|
||||
for name in repeated), 'Repeats do not match the complete first-seed inventory')
|
||||
return planned, primary
|
||||
|
||||
|
||||
def _stage_coverage(helper, experiment, rows, stage, planned):
|
||||
receipt = _small_json(helper, experiment, rows, 'audit/' + stage + '-complete.json')
|
||||
require(receipt.get('stage') == stage and receipt.get('pass_all') is True and receipt.get('training') is False and
|
||||
all(type(receipt.get(k)) is int and receipt[k] == v for k, v in
|
||||
(('trials', 144), ('primary_trials', 120), ('technical_repeats', 24))), 'Incomplete stage gate receipt: ' + stage)
|
||||
completed = receipt.get('completed')
|
||||
require(isinstance(completed, list) and len(completed) == 144, 'Stage receipt lacks 144 completed records')
|
||||
project = experiment.parents[1]
|
||||
for plan, entry in zip(planned, completed):
|
||||
require(isinstance(entry, dict) and set(entry) == {'name', 'repeat', 'output_hashes_sha256', 'comparison_sha256'}
|
||||
and entry['name'] == plan['name'] and type(entry['repeat']) is int and entry['repeat'] == plan['repeat'],
|
||||
'Stage receipt inventory order/identity mismatch')
|
||||
prefix = 'gpu/' + stage + '/' + plan['name'] + '-r' + str(plan['repeat']) + '/'
|
||||
for filename, key in (('output-hashes.json', 'output_hashes_sha256'), ('comparison.json', 'comparison_sha256')):
|
||||
require(prefix + filename in rows and rows[prefix + filename]['sha256'] == entry[key],
|
||||
'Stage receipt does not bind sealed output evidence: ' + prefix + filename)
|
||||
report = _small_json(helper, experiment, rows, prefix + 'comparison.json')
|
||||
mode = 'zero' if stage == 'zero' else ('repeat' if plan['repeat'] else 'self')
|
||||
folder = experiment / prefix.rstrip('/')
|
||||
case = project / 'experiments/phase4ak-apl-timing-validation/cases' / plan['cohort'] / plan['name']
|
||||
left = (project / 'experiments/phase4ak-apl-timing-validation/gpu' / plan['cohort'] /
|
||||
(plan['name'] + '-r' + str(plan['repeat'])) if stage == 'zero' else
|
||||
(experiment / 'gpu' / stage / (plan['name'] + '-r0') if plan['repeat'] else folder))
|
||||
require(report.get('pass_all') is True and report.get('overlay_name') == stage and report.get('mode') == mode and
|
||||
report.get('case_dir') == str(case) and report.get('left') == str(left) and report.get('right') == str(folder),
|
||||
'Stage comparison evidence has wrong case/stage/repeat identity')
|
||||
inventory = _small_json(helper, experiment, rows, prefix + 'output-hashes.json')
|
||||
require(isinstance(inventory, list) and inventory, 'Empty trial output inventory')
|
||||
expected = {name[len(prefix):]: {**r, 'path': name[len(prefix):]} for name, r in rows.items()
|
||||
if name.startswith(prefix) and name != prefix + 'output-hashes.json'}
|
||||
actual = {}
|
||||
for item in inventory:
|
||||
require(isinstance(item, dict) and set(item) == {'path', 'bytes', 'sha256'} and
|
||||
item['path'] not in actual, 'Invalid/duplicate trial output inventory')
|
||||
actual[item['path']] = item
|
||||
require(actual == expected, 'Trial output inventory contradicts complete experiment seal')
|
||||
return dict(pass_all=True, trials=144, primary_trials=120, technical_repeats=24,
|
||||
receipt_sha256=rows['audit/' + stage + '-complete.json']['sha256'])
|
||||
|
||||
|
||||
def _status_evidence(helper, experiment, rows, model):
|
||||
"""Verify declarations and their sealed coverage, never recompute science."""
|
||||
evidence = {name: _small_json(helper, experiment, rows, name) for name in REQUIRED_EVIDENCE}
|
||||
status = evidence['audit/final-status.json']
|
||||
science = evidence['audit/scientific-summary.json']
|
||||
integrity = evidence['regression/old-integrity-after.json']
|
||||
config = helper._json_bytes((model / 'config.json').read_bytes())
|
||||
require(isinstance(config, dict) and set(config) == set(EXPECTED_CONFIG) and all(
|
||||
type(config[k]) is type(v) and config[k] == v for k, v in EXPECTED_CONFIG.items()),
|
||||
'Changed Phase 4AM configuration/design contract')
|
||||
require(isinstance(status, dict) and status.get('status') in FINAL_STATUSES, 'Unrecognized Phase 4AM final status')
|
||||
require(all(status.get(key) is True for key in GATE_FIELDS), 'Declared PASS contradicts finalized gate flags')
|
||||
require(type(status.get('primary_trials_per_stage')) is int and status['primary_trials_per_stage'] == 120 and
|
||||
type(status.get('repeats_per_stage')) is int and status['repeats_per_stage'] == 24, 'Invalid finalized coverage')
|
||||
require(status.get('training') is False and status.get('learned_candidate') is False, 'Unexpected finalized scope claim')
|
||||
for record in (status, science):
|
||||
require(isinstance(record, dict) and all(record.get(k, False) is False for k in
|
||||
('learning_achieved', 'candidate_generated', 'conditioning_executed', 'full_scientific_goal_achieved')),
|
||||
'Unexpected learning/candidate scope claim')
|
||||
require(status.get('scientific_summary_sha256') == rows['audit/scientific-summary.json']['sha256'],
|
||||
'Final status scientific-summary SHA mismatch')
|
||||
require(isinstance(science, dict) and science.get('status') == status['status'] and
|
||||
science.get('zero_overlay_exact') is True and science.get('static25_repeat_exact') is True and
|
||||
science.get('training') is False and science.get('learned_candidate') is False and
|
||||
science.get('conditional_AL_not_a_nonzero_arithmetic_oracle') is True and science.get('scope') == SCIENCE_SCOPE,
|
||||
'Scientific summary scope/status contradiction')
|
||||
require(type(science.get('primary_trials_per_stage')) is int and science['primary_trials_per_stage'] == 120 and
|
||||
type(science.get('technical_repeats_per_stage')) is int and science['technical_repeats_per_stage'] == 24,
|
||||
'Scientific summary coverage mismatch')
|
||||
require('summaries/per-MBON-static-effects.parquet' in rows and science.get('per_MBON_sha256') ==
|
||||
rows['summaries/per-MBON-static-effects.parquet']['sha256'], 'Scientific per-MBON payload SHA mismatch')
|
||||
require(isinstance(integrity, dict) and integrity and all(isinstance(r, dict) and r.get('pass') is True
|
||||
for r in integrity.values()), 'Final status contradicts its frozen-integrity report')
|
||||
reference = _small_json(helper, experiment, rows, REFERENCE_EVIDENCE)
|
||||
require(isinstance(reference, dict), 'Invalid frozen reference report')
|
||||
trials = reference.get('trials')
|
||||
require(reference.get('pass_all') is True and isinstance(trials, list) and
|
||||
[r.get('trial') for r in trials if isinstance(r, dict)] == list(REFERENCE_NAMES) and len(trials) == 5 and
|
||||
all(isinstance(r.get('exact'), dict) and set(r['exact']) == {'step', 'neuron_idx', 'counts', 'v', 'g'} and
|
||||
all(value is True for value in r['exact'].values()) for r in trials),
|
||||
'Declared frozen-five PASS requires five exact trial records with the original ordered names')
|
||||
blocks = science.get('blocks')
|
||||
require(isinstance(blocks, dict) and set(blocks) == {c + '/' + m for c in COHORTS for m in MODES},
|
||||
'Missing/extra cohort-mode blocks')
|
||||
for block in blocks.values():
|
||||
require(isinstance(block, dict) and type(block.get('trials')) is int and block['trials'] == 30 and
|
||||
isinstance(block.get('populations'), dict) and set(block['populations']) == set(POPULATIONS),
|
||||
'Incomplete cohort-mode population block')
|
||||
for metrics in block['populations'].values():
|
||||
require(isinstance(metrics, dict) and set(metrics) == {'trials_event_change', 'trials_count_change',
|
||||
'mean_count_l1', 'mean_recruited_cells', 'mean_lost_cells'}, 'Invalid population metric fields')
|
||||
for key in ('trials_event_change', 'trials_count_change'):
|
||||
require(type(metrics[key]) is int and 0 <= metrics[key] <= 30, 'Invalid diagnostic count')
|
||||
for key in ('mean_count_l1', 'mean_recruited_cells', 'mean_lost_cells'):
|
||||
require(type(metrics[key]) in (int, float) and math.isfinite(metrics[key]) and metrics[key] >= 0,
|
||||
'Nonfinite/negative population metric')
|
||||
require(metrics['trials_count_change'] <= metrics['trials_event_change'], 'Count change without event change')
|
||||
# Zero effects remain publishable: this is not an effect-size gate.
|
||||
planned, primary = _planned_inventory(evidence['protocol/preregistered.json'], config)
|
||||
timing = science.get('timing')
|
||||
require(isinstance(timing, list) and len(timing) == 240 and all(isinstance(r, dict) for r in timing) and
|
||||
{(r.get('stage'), r.get('case')) for r in timing} == {(s, n) for s in STAGES for n in primary},
|
||||
'Scientific timing inventory lacks all 240 primary runs')
|
||||
for item in timing:
|
||||
require(all(type(item.get(k)) in (int, float) and math.isfinite(item[k]) and item[k] >= 0
|
||||
for k in ('wall_seconds', 'simulation_seconds')), 'Invalid diagnostic timing value')
|
||||
stages = {stage: _stage_coverage(helper, experiment, rows, stage, planned) for stage in STAGES}
|
||||
return status, reference, integrity, stages
|
||||
|
||||
|
||||
def publish(root=ROOT, helper_path=None):
|
||||
"""Publish the fixed AM registry exclusively; root override is for tests.
|
||||
|
||||
Only sealed small experiment evidence and the two model seal controls are
|
||||
copied. Every bulk payload is nevertheless verified before and after copying.
|
||||
An interrupted registry is preserved and cannot be overwritten or resumed.
|
||||
"""
|
||||
root = Path(root)
|
||||
require(root.is_absolute() and root.is_dir() and not root.is_symlink(), 'Invalid project root')
|
||||
root = root.resolve(strict=True)
|
||||
model, experiment, registry = (root / part for part in
|
||||
(MODEL_RELATIVE, EXPERIMENT_RELATIVE, REGISTRY_RELATIVE))
|
||||
require(not registry.exists() and not registry.is_symlink(), 'Registry already exists; no overwrite/resume')
|
||||
helper_path = root / HELPER_RELATIVE if helper_path is None else Path(helper_path)
|
||||
helper = load_helpers(helper_path)
|
||||
for path in (model, experiment, registry.parent):
|
||||
current = root
|
||||
for part in path.relative_to(root).parts:
|
||||
current = current / part
|
||||
require(not current.is_symlink(), 'Symlink in publication root path: ' + str(current))
|
||||
verified = {'model': helper.verify_seal(model), 'experiment': helper.verify_seal(experiment)}
|
||||
experiment_rows = {row['path']: row for row in verified['experiment']['files']}
|
||||
status, reference, integrity, stages = _status_evidence(helper, experiment, experiment_rows, model)
|
||||
plans, omitted = [], []
|
||||
for row in verified['experiment']['files'] + helper._control_rows(experiment, verified['experiment']):
|
||||
if PurePosixPath(row['path']).suffix.lower() in EVIDENCE_SUFFIXES and row['bytes'] <= MAX_EVIDENCE_BYTES:
|
||||
plans.append((experiment, row, 'evidence/' + row['path']))
|
||||
else:
|
||||
omitted.append(dict(path=row['path'], bytes=row['bytes'], sha256=row['sha256'],
|
||||
reason='exceeds_4_MiB' if row['bytes'] > MAX_EVIDENCE_BYTES else 'bulk_or_non_evidence_extension'))
|
||||
for row in helper._control_rows(model, verified['model']):
|
||||
require(row['bytes'] <= MAX_EVIDENCE_BYTES, 'Model seal control exceeds small-evidence limit')
|
||||
plans.append((model, row, 'model/' + row['path']))
|
||||
destinations = {target for _, _, target in plans}
|
||||
required_copies = ['manifest.json', 'checksums.sha256', *REQUIRED_EVIDENCE]
|
||||
if reference is not None:
|
||||
required_copies.append(REFERENCE_EVIDENCE)
|
||||
for relative in required_copies:
|
||||
require('evidence/' + relative in destinations, 'Required experiment evidence cannot be copied: ' + relative)
|
||||
require(len(destinations) == len(plans), 'Duplicate planned evidence destination')
|
||||
registry.parent.mkdir(parents=True, exist_ok=True)
|
||||
registry.mkdir(exist_ok=False)
|
||||
try:
|
||||
records = []
|
||||
for source_root, row, relative in plans:
|
||||
source = helper._regular_file(source_root.resolve(), row['path'])
|
||||
_copy(helper, source, registry / relative, row['bytes'], row['sha256'])
|
||||
records.append(dict(source=source.relative_to(root).as_posix(), copy=relative,
|
||||
bytes=row['bytes'], sha256=row['sha256']))
|
||||
after = {'model': helper.verify_seal(model), 'experiment': helper.verify_seal(experiment)}
|
||||
require(after == verified, 'A sealed tree changed during publication')
|
||||
require(helper._digest(helper_path) == HELPER_SHA256, 'Frozen helper changed during publication')
|
||||
proof = {name: {key: value for key, value in report.items() if key != 'files'}
|
||||
for name, report in verified.items()}
|
||||
_write_small_json(helper, registry / 'seal-verification.json', proof)
|
||||
_write_small_json(helper, registry / 'evidence-index.json', records)
|
||||
files = [dict(path=path.relative_to(registry).as_posix(), bytes=path.stat().st_size,
|
||||
sha256=helper._digest(path)) for path in sorted(registry.rglob('*')) if path.is_file()]
|
||||
receipt = dict(publication_complete=True, registry=registry.relative_to(root).as_posix(),
|
||||
project_root=str(root), declared_audit_status=status['status'],
|
||||
declared_gate_results={key: status[key] for key in GATE_FIELDS},
|
||||
declared_audit_counts={key: status[key] for key in COUNT_FIELDS},
|
||||
declared_diagnostic_scope='Exact zero-overlay D18 identity and fixed whole-brain static25 interface; no training or associative learning',
|
||||
declared_stage_coverage=stages, declared_scientific_summary_scope=SCIENCE_SCOPE,
|
||||
static_effect_size_gate_defined=False,
|
||||
scientific_summary_sha256=status['scientific_summary_sha256'],
|
||||
frozen_reference_report=dict(present=reference is not None,
|
||||
path='evidence/' + REFERENCE_EVIDENCE if reference is not None else None,
|
||||
sha256=experiment_rows[REFERENCE_EVIDENCE]['sha256'] if reference is not None else None,
|
||||
declared_pass_all=reference['pass_all'] if reference is not None else None,
|
||||
reported_trial_count=len(reference['trials']) if reference is not None else None,
|
||||
missing_is_not_a_success_or_fabricated_trial_failure=True),
|
||||
frozen_integrity_report=dict(path='evidence/regression/old-integrity-after.json',
|
||||
sha256=experiment_rows['regression/old-integrity-after.json']['sha256'],
|
||||
declared_pass_by_root={name: row['pass'] for name, row in integrity.items()}),
|
||||
frozen_helper_source=str(helper_path), frozen_helper_sha256=HELPER_SHA256,
|
||||
publisher_sha256=helper._digest(Path(__file__)), helper_globals_redirected=False,
|
||||
model_manifest_sha256=verified['model']['manifest_sha256'],
|
||||
experiment_manifest_sha256=verified['experiment']['manifest_sha256'],
|
||||
source_trees_verified_before_and_after_copy=True,
|
||||
copied_evidence_files=len(records), copied_evidence_bytes=sum(row['bytes'] for row in records),
|
||||
files=files, experiment_payload_not_copied=omitted,
|
||||
model_payload_not_duplicated=True, model_payload_remains_at=MODEL_RELATIVE.as_posix(),
|
||||
bulk_remains_at=EXPERIMENT_RELATIVE.as_posix(), complete_bulk_backup=False,
|
||||
scientific_gate_executed_by_publisher=False, scientific_gate_certified_by_publication=False,
|
||||
publication_success_is_not_scientific_pass=True, candidate_generated=False,
|
||||
learning_achieved=False, full_scientific_goal_achieved=False,
|
||||
old_heldout_dynamic_results_parsed=False, existing_files_modified=False,
|
||||
receipt_self_hash='Not self-referential; returned by publish() and printed by main()',
|
||||
limits=dict(max_selected_evidence_file_bytes=MAX_EVIDENCE_BYTES,
|
||||
selected_suffixes=sorted(EVIDENCE_SUFFIXES)))
|
||||
receipt_path = registry / 'publication-receipt.json'
|
||||
_write_small_json(helper, receipt_path, receipt)
|
||||
return dict(publication_complete=True, registry=str(registry), declared_audit_status=status['status'],
|
||||
copied_evidence_files=len(records), receipt_sha256=helper._digest(receipt_path),
|
||||
scientific_gate_executed_by_publisher=False, publication_success_is_not_scientific_pass=True)
|
||||
except Exception as error:
|
||||
failure = registry / 'publication-failure.json'
|
||||
if not failure.exists():
|
||||
_write_small_json(helper, failure, dict(publication_complete=False, type=type(error).__name__,
|
||||
message=str(error), partial_outputs_preserved=True, existing_files_modified=False))
|
||||
raise
|
||||
|
||||
|
||||
def main():
|
||||
print(json.dumps(publish(), ensure_ascii=False, allow_nan=False), flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
"""Pure in-memory SFTP plus temporary local destinations, never a real server."""
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import stat
|
||||
import tempfile
|
||||
import types
|
||||
import unittest
|
||||
|
||||
import fetch_phase4am as fetcher
|
||||
|
||||
|
||||
def data(value):
|
||||
return (json.dumps(value, sort_keys=True, allow_nan=False) + '\n').encode()
|
||||
|
||||
|
||||
def row(name, content):
|
||||
return dict(path=name, bytes=len(content), sha256=fetcher.sha(content))
|
||||
|
||||
|
||||
def sealed(root, payload):
|
||||
rows = [row(name, content) for name, content in sorted(payload.items())]
|
||||
manifest = data(dict(root=root, files=rows))
|
||||
checksums = '\n'.join([entry['sha256'] + ' ' + entry['path'] for entry in rows] +
|
||||
[fetcher.sha(manifest) + ' manifest.json']).encode() + b'\n'
|
||||
return dict(payload, **{'manifest.json': manifest, 'checksums.sha256': checksums}), dict(
|
||||
pass_all=True, root=root, payload_file_count=len(rows), payload_bytes=sum(x['bytes'] for x in rows),
|
||||
manifest_sha256=fetcher.sha(manifest), checksums_sha256=fetcher.sha(checksums))
|
||||
|
||||
|
||||
class FakeSFTP:
|
||||
def __init__(self, files):
|
||||
self.files = files
|
||||
self.opened = []
|
||||
self.hook = None
|
||||
self.symlinks = set()
|
||||
|
||||
def lstat(self, name):
|
||||
if name in self.symlinks:
|
||||
return types.SimpleNamespace(st_mode=stat.S_IFLNK | 0o777, st_size=1)
|
||||
if name in self.files:
|
||||
return types.SimpleNamespace(st_mode=stat.S_IFREG | 0o644, st_size=len(self.files[name]))
|
||||
if any(path.startswith(name.rstrip('/') + '/') for path in self.files):
|
||||
return types.SimpleNamespace(st_mode=stat.S_IFDIR | 0o755, st_size=0)
|
||||
raise FileNotFoundError(name)
|
||||
|
||||
def open(self, name, mode):
|
||||
if mode != 'rb':
|
||||
raise AssertionError('Remote write attempted')
|
||||
self.opened.append(name)
|
||||
if self.hook:
|
||||
self.hook(name)
|
||||
return io.BytesIO(self.files[name])
|
||||
|
||||
|
||||
def fixture(extra_experiment=None, extra_model=None):
|
||||
status = dict(status='INCOMPLETE-SYNTHETIC-AM-EVIDENCE', zero_overlay_exact=False,
|
||||
primary_trials_per_stage=120, candidate_generated=False, learning_achieved=False)
|
||||
mp = {'module.py': b'# synthetic, not model execution\n', 'config.json': data({'synthetic': True}),
|
||||
'never-download.npy': b'raw model bulk'}
|
||||
mp.update(extra_model or {})
|
||||
ep = {'audit/final-status.json': data(status), 'regression/old-integrity-after.json': data({'tree': {'pass': True}}),
|
||||
'audit/scientific-summary.json': data({'synthetic_scope': 'not scientific output'}),
|
||||
'protocol/preregistered.json': data({'synthetic_only': True}),
|
||||
'audit/zero-complete.json': data({'pass_all': False, 'synthetic_only': True}),
|
||||
'audit/static25-complete.json': data({'pass_all': False, 'synthetic_only': True}),
|
||||
'regression/frozen-five.json': data({'pass_all': False, 'trials': []}),
|
||||
'REPORT.md': b'Synthetic incomplete evidence.\n', 'tests.log': b'Synthetic test log.\n',
|
||||
'gpu/example/state.npz': b'large trajectory placeholder not requested',
|
||||
'gpu/example/tick-state-hashes.jsonl': b'non-whitelisted suffix'}
|
||||
ep.update(extra_experiment or {})
|
||||
model, model_proof = sealed(fetcher.MODEL_ROOT, mp)
|
||||
experiment, exp_proof = sealed(fetcher.REMOTE_ROOT + '/' + fetcher.EXPERIMENT_RELATIVE, ep)
|
||||
registry = {}
|
||||
index = []
|
||||
for name, content in experiment.items():
|
||||
if Path(name).suffix.lower() in fetcher.EVIDENCE_SUFFIXES and len(content) <= fetcher.MAX_FILE_BYTES:
|
||||
dest = 'evidence/' + name
|
||||
registry[dest] = content
|
||||
index.append(dict(source=fetcher.EXPERIMENT_RELATIVE + '/' + name,
|
||||
copy=dest, bytes=len(content), sha256=fetcher.sha(content)))
|
||||
for name in fetcher.CONTROLS:
|
||||
dest = 'model/' + name; content = model[name]
|
||||
registry[dest] = content
|
||||
index.append(dict(source=fetcher.MODEL_RELATIVE + '/' + name,
|
||||
copy=dest, bytes=len(content), sha256=fetcher.sha(content)))
|
||||
registry['seal-verification.json'] = data(dict(model=model_proof, experiment=exp_proof))
|
||||
registry['evidence-index.json'] = data(index)
|
||||
receipt = dict(publication_complete=True, registry=fetcher.REGISTRY_RELATIVE, project_root=fetcher.REMOTE_ROOT,
|
||||
source_trees_verified_before_and_after_copy=True, publication_success_is_not_scientific_pass=True,
|
||||
complete_bulk_backup=False, scientific_gate_executed_by_publisher=False,
|
||||
scientific_gate_certified_by_publication=False, candidate_generated=False, learning_achieved=False,
|
||||
model_manifest_sha256=model_proof['manifest_sha256'], experiment_manifest_sha256=exp_proof['manifest_sha256'],
|
||||
files=[row(name, content) for name, content in sorted(registry.items())],
|
||||
limits=dict(max_selected_evidence_file_bytes=fetcher.MAX_FILE_BYTES, selected_suffixes=sorted(fetcher.EVIDENCE_SUFFIXES)),
|
||||
declared_audit_status=status['status'], declared_gate_results={'zero_overlay_exact': False},
|
||||
declared_audit_counts={'primary_trials_per_stage': 120})
|
||||
registry['publication-receipt.json'] = data(receipt)
|
||||
files = {fetcher.MODEL_ROOT + '/' + name: content for name, content in model.items()}
|
||||
files.update({fetcher.REGISTRY_ROOT + '/' + name: content for name, content in registry.items()})
|
||||
return FakeSFTP(files), fetcher.sha(registry['publication-receipt.json'])
|
||||
|
||||
|
||||
class FetchTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory(prefix='phase4am-fetch-synthetic-')
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.destination = Path(self.temp.name).resolve() / 'report'
|
||||
self.sftp, self.receipt_sha = fixture()
|
||||
|
||||
def receipt(self):
|
||||
return fetcher.json_bytes(self.sftp.files[fetcher.REGISTRY_ROOT + '/publication-receipt.json'])
|
||||
|
||||
def resign(self, receipt):
|
||||
raw = data(receipt)
|
||||
self.sftp.files[fetcher.REGISTRY_ROOT + '/publication-receipt.json'] = raw
|
||||
self.receipt_sha = fetcher.sha(raw)
|
||||
|
||||
def get(self, **kwargs):
|
||||
return fetcher.fetch(self.sftp, self.destination, self.receipt_sha, **kwargs)
|
||||
|
||||
def test_only_receipt_small_files_and_model_source_with_hashes(self):
|
||||
result = self.get()
|
||||
self.assertTrue(result['fetch_complete'])
|
||||
self.assertTrue(result['both_manifest_self_checksums_verified'])
|
||||
self.assertTrue(result['fetch_success_is_not_scientific_pass'])
|
||||
self.assertFalse(result['complete_bulk_backup'])
|
||||
self.assertFalse(result['all_remote_experiment_bulk_rehashed_locally'])
|
||||
self.assertEqual(result['declared_audit_status'], 'INCOMPLETE-SYNTHETIC-AM-EVIDENCE')
|
||||
self.assertTrue((self.destination / 'audit/final-status.json').is_file())
|
||||
self.assertTrue((self.destination / 'model/module.py').is_file())
|
||||
self.assertTrue((self.destination / 'registry/publication-receipt.json').is_file())
|
||||
self.assertFalse((self.destination / 'gpu/example/state.npz').exists())
|
||||
self.assertFalse((self.destination / 'model/never-download.npy').exists())
|
||||
self.assertFalse(any('interpretation' in path or 'phase4aj' in path or path.endswith(('.npy', '.npz', '.jsonl'))
|
||||
for path in self.sftp.opened))
|
||||
for entry in result['files']:
|
||||
self.assertEqual(fetcher.sha((self.destination / entry['local']).read_bytes()), entry['sha256'])
|
||||
|
||||
def test_out_of_band_sha_is_mandatory_and_checked_before_other_remote_reads(self):
|
||||
for checksum in ('', 'g' * 64, '0' * 64):
|
||||
self.sftp.opened.clear()
|
||||
with self.subTest(checksum=checksum), self.assertRaises(ValueError):
|
||||
fetcher.fetch(self.sftp, self.destination, checksum)
|
||||
self.assertFalse(self.destination.exists())
|
||||
self.assertTrue(all(path.endswith('/publication-receipt.json') for path in self.sftp.opened))
|
||||
|
||||
def test_no_overwrite_or_resume_even_empty_existing_directory(self):
|
||||
self.destination.mkdir()
|
||||
with self.assertRaisesRegex(ValueError, 'exists'):
|
||||
self.get()
|
||||
self.assertFalse(self.sftp.opened)
|
||||
self.assertEqual(list(self.destination.iterdir()), [])
|
||||
|
||||
def test_malformed_or_non_small_receipt_paths_fail_without_download(self):
|
||||
for name, size in (('../escape.json', 1), ('evidence/bulk.npy', 1), ('interpretation-metrics.json', 1),
|
||||
('evidence/huge.json', fetcher.MAX_FILE_BYTES + 1), ('evidence/C:/escape.json', 1)):
|
||||
with self.subTest(name=name):
|
||||
self.sftp, self.receipt_sha = fixture()
|
||||
receipt = self.receipt()
|
||||
receipt['files'].append(dict(path=name, bytes=size, sha256='a' * 64))
|
||||
self.resign(receipt)
|
||||
with self.assertRaises(ValueError):
|
||||
self.get()
|
||||
self.assertFalse(self.destination.exists())
|
||||
self.assertEqual(self.sftp.opened, [fetcher.REGISTRY_ROOT + '/publication-receipt.json'])
|
||||
|
||||
def test_manifest_self_checksum_is_not_trusted_from_receipt_alone(self):
|
||||
name = 'evidence/checksums.sha256'; path = fetcher.REGISTRY_ROOT + '/' + name
|
||||
lines = self.sftp.files[path].decode().splitlines()
|
||||
lines[-1] = '0' * 64 + ' manifest.json'
|
||||
changed = ('\n'.join(lines) + '\n').encode()
|
||||
self.sftp.files[path] = changed
|
||||
receipt = self.receipt()
|
||||
next(entry for entry in receipt['files'] if entry['path'] == name).update(bytes=len(changed), sha256=fetcher.sha(changed))
|
||||
self.resign(receipt)
|
||||
with self.assertRaisesRegex(ValueError, 'self-SHA'):
|
||||
self.get()
|
||||
self.assertFalse(self.destination.exists())
|
||||
|
||||
def test_model_original_seal_must_equal_registry_anchor(self):
|
||||
self.sftp.files[fetcher.MODEL_ROOT + '/manifest.json'] += b' '
|
||||
with self.assertRaises(ValueError):
|
||||
self.get()
|
||||
self.assertFalse(self.destination.exists())
|
||||
|
||||
def test_corrupt_selected_payload_preserves_partial_and_blocks_resume(self):
|
||||
path = fetcher.REGISTRY_ROOT + '/evidence/REPORT.md'
|
||||
self.sftp.files[path] = b'x' * len(self.sftp.files[path])
|
||||
with self.assertRaisesRegex(ValueError, 'SHA'):
|
||||
self.get()
|
||||
self.assertTrue((self.destination / 'fetch-failure.json').is_file())
|
||||
self.assertFalse((self.destination / 'fetch-verification.json').exists())
|
||||
with self.assertRaisesRegex(ValueError, 'exists'):
|
||||
self.get()
|
||||
|
||||
def test_model_source_hash_is_verified_without_fetching_other_payloads(self):
|
||||
path = fetcher.MODEL_ROOT + '/module.py'
|
||||
self.sftp.files[path] = b'x' * len(self.sftp.files[path])
|
||||
with self.assertRaisesRegex(ValueError, 'SHA'):
|
||||
self.get()
|
||||
self.assertTrue((self.destination / 'fetch-failure.json').is_file())
|
||||
self.assertFalse(any(path.endswith('never-download.npy') for path in self.sftp.opened))
|
||||
|
||||
def test_local_path_collision_and_total_budget_fail_before_destination(self):
|
||||
self.sftp, self.receipt_sha = fixture(extra_experiment={'model/module.py.json': data({'not_source': True}),
|
||||
'model/config.json': data({'collision': True})})
|
||||
with self.assertRaisesRegex(ValueError, 'collision'):
|
||||
self.get()
|
||||
self.assertFalse(self.destination.exists())
|
||||
self.sftp, self.receipt_sha = fixture()
|
||||
with self.assertRaisesRegex(ValueError, 'total download budget'):
|
||||
self.get(max_total_bytes=1)
|
||||
self.assertFalse(self.destination.exists())
|
||||
|
||||
def test_remote_symlink_rejected_without_open(self):
|
||||
path = fetcher.REGISTRY_ROOT + '/publication-receipt.json'
|
||||
self.sftp.symlinks.add(path)
|
||||
with self.assertRaisesRegex(ValueError, 'symlink'):
|
||||
self.get()
|
||||
self.assertFalse(self.sftp.opened)
|
||||
|
||||
def test_anchor_changed_during_copy_fails_final_check(self):
|
||||
receipt_path = fetcher.REGISTRY_ROOT + '/publication-receipt.json'
|
||||
def change_on_source(name):
|
||||
if name == fetcher.MODEL_ROOT + '/module.py':
|
||||
self.sftp.files[receipt_path] += b' '
|
||||
self.sftp.hook = change_on_source
|
||||
with self.assertRaises(ValueError):
|
||||
self.get()
|
||||
self.assertTrue((self.destination / 'fetch-failure.json').is_file())
|
||||
self.assertFalse((self.destination / 'fetch-verification.json').exists())
|
||||
|
||||
def test_no_import_network_and_cli_rejects_unknown_hosts(self):
|
||||
source = Path(fetcher.__file__).read_text(encoding='utf-8')
|
||||
self.assertIn('paramiko.RejectPolicy()', source)
|
||||
self.assertIn('client.load_system_host_keys(str(args.known_hosts))', source)
|
||||
self.assertIn("default=Path.home() / '.ssh/known_hosts'", source)
|
||||
self.assertNotIn('AutoAddPolicy', source)
|
||||
self.assertNotIn('interpretation-metrics.json', source)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
"""Temporary synthetic seals only; never publish/register any real evidence."""
|
||||
import hashlib
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import publish_phase4am as publication
|
||||
|
||||
|
||||
HELPER = Path(__file__).resolve().parents[1] / 'pn-recruitment-audit-v1/publication.py'
|
||||
if not HELPER.is_file():
|
||||
HELPER = publication.ROOT / publication.HELPER_RELATIVE
|
||||
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def write(path, value):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(value, indent=2), encoding='utf-8')
|
||||
|
||||
|
||||
def seal(root):
|
||||
rows = [dict(path=path.relative_to(root).as_posix(), bytes=path.stat().st_size, sha256=sha(path))
|
||||
for path in sorted(root.rglob('*')) if path.is_file() and
|
||||
path.name not in ('manifest.json', 'checksums.sha256') and '__pycache__' not in path.parts]
|
||||
write(root / 'manifest.json', dict(root=str(root), files=rows))
|
||||
lines = [row['sha256'] + ' ' + row['path'] for row in rows]
|
||||
lines.append(sha(root / 'manifest.json') + ' manifest.json')
|
||||
(root / 'checksums.sha256').write_text('\n'.join(lines) + '\n', encoding='utf-8')
|
||||
|
||||
|
||||
class PublicationTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
temporary = tempfile.TemporaryDirectory(prefix='phase4am-publication-synthetic-')
|
||||
self.addCleanup(temporary.cleanup)
|
||||
self.root = Path(temporary.name).resolve()
|
||||
self.model = self.root / publication.MODEL_RELATIVE
|
||||
self.experiment = self.root / publication.EXPERIMENT_RELATIVE
|
||||
self.registry = self.root / publication.REGISTRY_RELATIVE
|
||||
self.model.mkdir(parents=True)
|
||||
self.experiment.mkdir(parents=True)
|
||||
(self.model / 'module.py').write_text('# synthetic, not a model\n', encoding='utf-8')
|
||||
self.config = copy.deepcopy(publication.EXPECTED_CONFIG)
|
||||
write(self.model / 'config.json', self.config)
|
||||
self.planned = []
|
||||
for cohort in publication.COHORTS:
|
||||
for mode in publication.MODES:
|
||||
for odor in range(6):
|
||||
for seed in range(11, 16):
|
||||
name = f'{cohort}-{mode}-odor{odor}-seed{seed}'
|
||||
for repeat in ((0, 1) if seed == 11 else (0,)):
|
||||
self.planned.append(dict(name=name, cohort=cohort, mode=mode,
|
||||
odor_id=f'{cohort}-odor{odor}', seed=seed, repeat=repeat))
|
||||
self.registration = dict(config=self.config, planned=self.planned, training=False,
|
||||
candidate_generated=False, before_new_brain_trajectories=True)
|
||||
write(self.experiment / 'protocol/preregistered.json', self.registration)
|
||||
for stage in publication.STAGES:
|
||||
completed = []
|
||||
for row in self.planned:
|
||||
key = row['name'] + '-r' + str(row['repeat'])
|
||||
folder = self.experiment / 'gpu' / stage / key
|
||||
case = self.root / 'experiments/phase4ak-apl-timing-validation/cases' / row['cohort'] / row['name']
|
||||
mode = 'zero' if stage == 'zero' else ('repeat' if row['repeat'] else 'self')
|
||||
left = (self.root / 'experiments/phase4ak-apl-timing-validation/gpu' / row['cohort'] / key
|
||||
if stage == 'zero' else (self.experiment / 'gpu' / stage / (row['name'] + '-r0')
|
||||
if row['repeat'] else folder))
|
||||
write(folder / 'comparison.json', dict(pass_all=True, overlay_name=stage, mode=mode,
|
||||
case_dir=str(case), left=str(left), right=str(folder)))
|
||||
item = folder / 'comparison.json'
|
||||
write(folder / 'output-hashes.json', [dict(path=item.name, bytes=item.stat().st_size, sha256=sha(item))])
|
||||
completed.append(dict(name=row['name'], repeat=row['repeat'],
|
||||
output_hashes_sha256=sha(folder / 'output-hashes.json'), comparison_sha256=sha(item)))
|
||||
write(self.experiment / 'audit' / (stage + '-complete.json'), dict(stage=stage, pass_all=True,
|
||||
trials=144, primary_trials=120, technical_repeats=24, completed=completed, training=False))
|
||||
table = self.experiment / 'summaries/per-MBON-static-effects.parquet'
|
||||
table.parent.mkdir(parents=True)
|
||||
table.write_bytes(b'synthetic table placeholder, never parsed as science')
|
||||
self.science = dict(status=publication.FINAL_STATUS, primary_trials_per_stage=120,
|
||||
technical_repeats_per_stage=24, zero_overlay_exact=True, static25_repeat_exact=True,
|
||||
training=False, learned_candidate=False, conditional_AL_not_a_nonzero_arithmetic_oracle=True,
|
||||
scope=publication.SCIENCE_SCOPE, per_MBON_sha256=sha(table),
|
||||
timing=[dict(stage=s, case=r['name'], wall_seconds=.01, simulation_seconds=.005)
|
||||
for s in publication.STAGES for r in self.planned if r['repeat'] == 0],
|
||||
blocks={c + '/' + m: dict(trials=30, populations={p: dict(trials_event_change=0,
|
||||
trials_count_change=0, mean_count_l1=0., mean_recruited_cells=0., mean_lost_cells=0.)
|
||||
for p in publication.POPULATIONS}) for c in publication.COHORTS for m in publication.MODES})
|
||||
write(self.experiment / 'audit/scientific-summary.json', self.science)
|
||||
self.status = dict(status=publication.FINAL_STATUS, primary_trials_per_stage=120, repeats_per_stage=24,
|
||||
zero_overlay_exact=True, static25_repeat_exact=True, old_integrity_pass=True,
|
||||
frozen_five_reference_pass=True, frozen_eligibility_and_mask_unchanged=True, base_weights_unchanged=True,
|
||||
training=False, learned_candidate=False,
|
||||
scientific_summary_sha256=sha(self.experiment / 'audit/scientific-summary.json'))
|
||||
write(self.experiment / 'audit/final-status.json', self.status)
|
||||
write(self.experiment / 'regression/frozen-five.json', dict(pass_all=True,
|
||||
trials=[dict(trial=name, exact={k: True for k in ('step', 'neuron_idx', 'counts', 'v', 'g')})
|
||||
for name in publication.REFERENCE_NAMES]))
|
||||
write(self.experiment / 'regression/old-integrity-after.json', {'synthetic-old-tree': {'pass': True}})
|
||||
write(self.experiment / 'nested/case/summary.json', {'synthetic': True})
|
||||
(self.experiment / 'REPORT.md').write_text('Synthetic zero-effect diagnostic.\n', encoding='utf-8')
|
||||
(self.experiment / 'tests.log').write_text('Synthetic log\n', encoding='utf-8')
|
||||
for suffix in ('.npy', '.npz', '.parquet'):
|
||||
(self.experiment / ('bulk' + suffix)).write_bytes(b'synthetic bulk bytes, not scientific data')
|
||||
seal(self.model)
|
||||
seal(self.experiment)
|
||||
|
||||
def publish(self):
|
||||
return publication.publish(self.root, HELPER)
|
||||
|
||||
def check_declarations(self):
|
||||
seal(self.experiment)
|
||||
rows = {r['path']: r for r in json.loads((self.experiment / 'manifest.json').read_text())['files']}
|
||||
return publication._status_evidence(publication.load_helpers(HELPER), self.experiment, rows, self.model)
|
||||
|
||||
def write_science(self, science):
|
||||
write(self.experiment / 'audit/scientific-summary.json', science)
|
||||
write(self.experiment / 'audit/final-status.json', dict(self.status,
|
||||
scientific_summary_sha256=sha(self.experiment / 'audit/scientific-summary.json')))
|
||||
|
||||
def test_stage_receipts_require_both_full_144_inventories(self):
|
||||
for stage in publication.STAGES:
|
||||
path = self.experiment / 'audit' / (stage + '-complete.json')
|
||||
original = json.loads(path.read_text())
|
||||
for change in ({'trials': 143}, {'primary_trials': 119}, {'technical_repeats': 23},
|
||||
{'completed': original['completed'][:-1]}, {'pass_all': False}, {'stage': 'other'}):
|
||||
with self.subTest(stage=stage, change=list(change)):
|
||||
write(path, dict(original, **change))
|
||||
with self.assertRaises(ValueError):
|
||||
self.check_declarations()
|
||||
write(path, original)
|
||||
|
||||
def test_stage_receipt_hash_and_order_cannot_hide_wrong_case(self):
|
||||
path = self.experiment / 'audit/static25-complete.json'
|
||||
original = json.loads(path.read_text())
|
||||
for bad in ('hash', 'duplicate', 'reorder'):
|
||||
receipt = copy.deepcopy(original)
|
||||
if bad == 'hash':
|
||||
receipt['completed'][0]['comparison_sha256'] = '0' * 64
|
||||
elif bad == 'duplicate':
|
||||
receipt['completed'][1] = copy.deepcopy(receipt['completed'][0])
|
||||
else:
|
||||
receipt['completed'][0], receipt['completed'][1] = receipt['completed'][1], receipt['completed'][0]
|
||||
with self.subTest(bad=bad):
|
||||
write(path, receipt)
|
||||
with self.assertRaises(ValueError):
|
||||
self.check_declarations()
|
||||
|
||||
def test_reanchored_comparison_cannot_change_stage_case_or_repeat_mode(self):
|
||||
stage_path = self.experiment / 'audit/static25-complete.json'
|
||||
original_stage = json.loads(stage_path.read_text())
|
||||
plan = self.planned[0]
|
||||
folder = self.experiment / 'gpu/static25' / (plan['name'] + '-r0')
|
||||
comparison = folder / 'comparison.json'
|
||||
inventory = folder / 'output-hashes.json'
|
||||
original_report = json.loads(comparison.read_text())
|
||||
for key, value in (('mode', 'repeat'), ('overlay_name', 'zero'), ('case_dir', '/wrong-case'),
|
||||
('left', '/wrong-reference'), ('right', '/wrong-output')):
|
||||
with self.subTest(key=key):
|
||||
write(comparison, dict(original_report, **{key: value}))
|
||||
write(inventory, [dict(path=comparison.name, bytes=comparison.stat().st_size, sha256=sha(comparison))])
|
||||
receipt = copy.deepcopy(original_stage)
|
||||
receipt['completed'][0].update(comparison_sha256=sha(comparison), output_hashes_sha256=sha(inventory))
|
||||
write(stage_path, receipt)
|
||||
with self.assertRaisesRegex(ValueError, 'wrong case/stage/repeat identity'):
|
||||
self.check_declarations()
|
||||
|
||||
def test_preregistered_scientific_coverage_and_first_seed_repeats_required(self):
|
||||
for mode in ('missing', 'duplicate', 'wrong_repeat_seed', 'training'):
|
||||
reg = copy.deepcopy(self.registration)
|
||||
if mode == 'missing':
|
||||
reg['planned'].pop()
|
||||
elif mode == 'duplicate':
|
||||
reg['planned'][-1] = copy.deepcopy(reg['planned'][0])
|
||||
elif mode == 'wrong_repeat_seed':
|
||||
next(r for r in reg['planned'] if r['repeat'] == 1)['seed'] += 1
|
||||
else:
|
||||
reg['training'] = True
|
||||
with self.subTest(mode=mode):
|
||||
write(self.experiment / 'protocol/preregistered.json', reg)
|
||||
with self.assertRaises(ValueError):
|
||||
self.check_declarations()
|
||||
|
||||
def test_scientific_population_metrics_and_explicit_scopes_required(self):
|
||||
for defect in ('missing_population', 'nonfinite', 'negative', 'bool_count', 'more_than30',
|
||||
'count_without_event', 'AL_oracle_claim', 'learning', 'timing_missing', 'table_hash'):
|
||||
science = copy.deepcopy(self.science)
|
||||
pops = science['blocks'][next(iter(science['blocks']))]['populations']
|
||||
if defect == 'missing_population':
|
||||
pops.pop('motor')
|
||||
elif defect in ('nonfinite', 'negative'):
|
||||
pops['KC']['mean_count_l1'] = float('nan') if defect == 'nonfinite' else -1.
|
||||
elif defect in ('bool_count', 'more_than30'):
|
||||
pops['KC']['trials_event_change'] = True if defect == 'bool_count' else 31
|
||||
elif defect == 'count_without_event':
|
||||
pops['KC']['trials_count_change'] = 1
|
||||
elif defect == 'AL_oracle_claim':
|
||||
science['conditional_AL_not_a_nonzero_arithmetic_oracle'] = False
|
||||
elif defect == 'learning':
|
||||
science['learning_achieved'] = True
|
||||
elif defect == 'timing_missing':
|
||||
science['timing'].pop()
|
||||
else:
|
||||
science['per_MBON_sha256'] = '0' * 64
|
||||
with self.subTest(defect=defect):
|
||||
self.write_science(science)
|
||||
with self.assertRaises(ValueError):
|
||||
self.check_declarations()
|
||||
|
||||
def test_five_refs_require_original_names_and_nonempty_exact_keys(self):
|
||||
path = self.experiment / 'regression/frozen-five.json'
|
||||
original = json.loads(path.read_text())
|
||||
for defect in ('duplicate_name', 'unknown_name', 'empty_exact', 'missing_exact', 'non_bool'):
|
||||
refs = copy.deepcopy(original)
|
||||
if defect == 'duplicate_name':
|
||||
refs['trials'][1]['trial'] = refs['trials'][0]['trial']
|
||||
elif defect == 'unknown_name':
|
||||
refs['trials'][0]['trial'] = 'invented-reference'
|
||||
elif defect == 'empty_exact':
|
||||
refs['trials'][0]['exact'] = {}
|
||||
elif defect == 'missing_exact':
|
||||
refs['trials'][0]['exact'].pop('g')
|
||||
else:
|
||||
refs['trials'][0]['exact']['g'] = 1
|
||||
with self.subTest(defect=defect):
|
||||
write(path, refs)
|
||||
with self.assertRaises(ValueError):
|
||||
self.check_declarations()
|
||||
|
||||
def test_completed_zero_effect_diagnostic_is_not_regraded_as_learning(self):
|
||||
before = {str(p): sha(p) for base in (self.model, self.experiment) for p in base.rglob('*') if p.is_file()}
|
||||
result = self.publish()
|
||||
self.assertTrue(result['publication_complete'])
|
||||
receipt = json.loads((self.registry / 'publication-receipt.json').read_text(encoding='utf-8'))
|
||||
self.assertEqual(receipt['declared_audit_status'], publication.FINAL_STATUS)
|
||||
self.assertFalse(receipt['static_effect_size_gate_defined'])
|
||||
self.assertEqual(set(receipt['declared_stage_coverage']), {'zero', 'static25'})
|
||||
self.assertEqual(receipt['declared_audit_counts'], {'primary_trials_per_stage': 120, 'repeats_per_stage': 24})
|
||||
self.assertFalse(receipt['learning_achieved'])
|
||||
self.assertTrue(receipt['publication_success_is_not_scientific_pass'])
|
||||
self.assertEqual(receipt['scientific_summary_sha256'], self.status['scientific_summary_sha256'])
|
||||
for suffix in ('.npy', '.npz', '.parquet'):
|
||||
self.assertFalse((self.registry / ('evidence/bulk' + suffix)).exists())
|
||||
for row in receipt['files']:
|
||||
self.assertEqual(sha(self.registry / row['path']), row['sha256'])
|
||||
self.assertEqual(before, {str(p): sha(p) for base in (self.model, self.experiment) for p in base.rglob('*') if p.is_file()})
|
||||
|
||||
def test_changed_config_blocks_before_destination(self):
|
||||
for key, value in (('phase', '4AK'), ('primary_trials', 119), ('candidate_edges', 14231),
|
||||
('max_fractional_depression', .5), ('training', True), ('virtual_gate_tick', 901),
|
||||
('apl_delay_steps', 0)):
|
||||
with self.subTest(key=key):
|
||||
write(self.model / 'config.json', dict(self.config, **{key: value}))
|
||||
seal(self.model)
|
||||
with self.assertRaisesRegex(ValueError, 'configuration/design'):
|
||||
self.publish()
|
||||
self.assertFalse(self.registry.exists())
|
||||
|
||||
def test_summary_hash_counts_and_scope_must_match(self):
|
||||
for changed in ({'scientific_summary_sha256': '0' * 64}, {'primary_trials_per_stage': 119},
|
||||
{'repeats_per_stage': 23}, {'learned_candidate': True}, {'zero_overlay_exact': 'true'},
|
||||
{'old_integrity_pass': False}, {'base_weights_unchanged': False}):
|
||||
with self.subTest(changed=changed):
|
||||
write(self.experiment / 'audit/final-status.json', dict(self.status, **changed))
|
||||
seal(self.experiment)
|
||||
with self.assertRaises(ValueError):
|
||||
self.publish()
|
||||
self.assertFalse(self.registry.exists())
|
||||
|
||||
def test_missing_or_corrupt_scientific_summary_is_rejected(self):
|
||||
path = self.experiment / 'audit/scientific-summary.json'
|
||||
path.unlink()
|
||||
seal(self.experiment)
|
||||
with self.assertRaisesRegex(ValueError, 'Required finalized evidence absent'):
|
||||
self.publish()
|
||||
self.assertFalse(self.registry.exists())
|
||||
|
||||
def test_complete_status_requires_all_four_complete_blocks(self):
|
||||
value = dict(self.science, blocks={})
|
||||
write(self.experiment / 'audit/scientific-summary.json', value)
|
||||
write(self.experiment / 'audit/final-status.json', dict(self.status,
|
||||
scientific_summary_sha256=sha(self.experiment / 'audit/scientific-summary.json')))
|
||||
seal(self.experiment)
|
||||
with self.assertRaisesRegex(ValueError, 'cohort-mode blocks'):
|
||||
self.publish()
|
||||
|
||||
def test_false_reference_exactness_is_not_hidden_by_pass_all(self):
|
||||
write(self.experiment / 'regression/frozen-five.json', dict(pass_all=True,
|
||||
trials=[dict(exact={'step': False})] * 5))
|
||||
seal(self.experiment)
|
||||
with self.assertRaisesRegex(ValueError, 'five exact trial'):
|
||||
self.publish()
|
||||
self.assertFalse(self.registry.exists())
|
||||
|
||||
def test_unknown_or_unfinalized_status_is_not_promoted(self):
|
||||
for status in ('INCOMPLETE-NOT-SEALED', 'PASS-FROZEN-D18-INDEPENDENT-VALIDATION'):
|
||||
write(self.experiment / 'audit/final-status.json', dict(self.status, status=status))
|
||||
seal(self.experiment)
|
||||
with self.assertRaisesRegex(ValueError, 'Unrecognized Phase 4AM'):
|
||||
self.publish()
|
||||
self.assertFalse(self.registry.exists())
|
||||
|
||||
def test_manifest_path_escape_rejected_before_destination(self):
|
||||
path = self.experiment / 'manifest.json'
|
||||
manifest = json.loads(path.read_text(encoding='utf-8'))
|
||||
manifest['files'][0]['path'] = '../escape.json'
|
||||
write(path, manifest)
|
||||
controls = self.experiment / 'checksums.sha256'
|
||||
controls.write_text('\n'.join([r['sha256'] + ' ' + r['path'] for r in manifest['files']] +
|
||||
[sha(path) + ' manifest.json']) + '\n', encoding='utf-8')
|
||||
with self.assertRaisesRegex(ValueError, 'escaping relative path'):
|
||||
self.publish()
|
||||
self.assertFalse(self.registry.exists())
|
||||
|
||||
def test_completed_status_without_frozen_reference_is_not_publishable(self):
|
||||
(self.experiment / 'regression/frozen-five.json').unlink()
|
||||
seal(self.experiment)
|
||||
with self.assertRaisesRegex(ValueError, 'Required finalized evidence absent'):
|
||||
self.publish()
|
||||
self.assertFalse(self.registry.exists())
|
||||
|
||||
def test_existing_registry_never_overwritten_or_resumed(self):
|
||||
self.registry.mkdir(parents=True)
|
||||
sentinel = self.registry / 'keep.txt'
|
||||
sentinel.write_bytes(b'keep these bytes')
|
||||
with self.assertRaisesRegex(ValueError, 'Registry already exists'):
|
||||
self.publish()
|
||||
self.assertEqual(sentinel.read_bytes(), b'keep these bytes')
|
||||
self.assertEqual(list(self.registry.iterdir()), [sentinel])
|
||||
|
||||
def test_missing_integrity_report_blocks_before_destination(self):
|
||||
(self.experiment / 'regression/old-integrity-after.json').unlink()
|
||||
seal(self.experiment)
|
||||
with self.assertRaisesRegex(ValueError, 'Required finalized evidence absent'):
|
||||
self.publish()
|
||||
self.assertFalse(self.registry.exists())
|
||||
|
||||
def test_bulk_corruption_blocks_even_though_bulk_is_not_copied(self):
|
||||
path = self.experiment / 'bulk.npy'
|
||||
path.write_bytes(b'x' * path.stat().st_size)
|
||||
with self.assertRaisesRegex(ValueError, 'Payload SHA-256 mismatch'):
|
||||
self.publish()
|
||||
self.assertFalse(self.registry.exists())
|
||||
|
||||
def test_unlisted_payload_and_bad_manifest_self_hash_fail(self):
|
||||
extra = self.experiment / 'unlisted.csv'
|
||||
extra.write_text('unlisted\n')
|
||||
with self.assertRaisesRegex(ValueError, 'unlisted/missing'):
|
||||
self.publish()
|
||||
extra.unlink()
|
||||
controls = self.experiment / 'checksums.sha256'
|
||||
lines = controls.read_text().splitlines()
|
||||
lines[-1] = '0' * 64 + ' manifest.json'
|
||||
controls.write_text('\n'.join(lines) + '\n')
|
||||
with self.assertRaisesRegex(ValueError, 'manifest self-checksum'):
|
||||
self.publish()
|
||||
self.assertFalse(self.registry.exists())
|
||||
|
||||
def test_large_json_verified_but_not_copied(self):
|
||||
path = self.experiment / 'large.json'
|
||||
path.write_bytes(b' ' * (publication.MAX_EVIDENCE_BYTES + 1))
|
||||
seal(self.experiment)
|
||||
self.publish()
|
||||
self.assertFalse((self.registry / 'evidence/large.json').exists())
|
||||
receipt = json.loads((self.registry / 'publication-receipt.json').read_text(encoding='utf-8'))
|
||||
self.assertEqual(next(r for r in receipt['experiment_payload_not_copied'] if r['path'] == 'large.json')['reason'],
|
||||
'exceeds_4_MiB')
|
||||
|
||||
def test_interrupted_copy_retained_and_cannot_resume(self):
|
||||
with mock.patch.object(publication, '_copy', side_effect=OSError('synthetic interrupted copy')):
|
||||
with self.assertRaisesRegex(OSError, 'synthetic interrupted copy'):
|
||||
self.publish()
|
||||
self.assertTrue((self.registry / 'publication-failure.json').is_file())
|
||||
self.assertFalse((self.registry / 'publication-receipt.json').exists())
|
||||
with self.assertRaisesRegex(ValueError, 'Registry already exists'):
|
||||
self.publish()
|
||||
|
||||
def test_after_copy_full_verification_detects_bulk_change(self):
|
||||
original = publication._copy
|
||||
changed = False
|
||||
|
||||
def copy_then_change(helper, source, destination, size, checksum):
|
||||
nonlocal changed
|
||||
original(helper, source, destination, size, checksum)
|
||||
if not changed:
|
||||
changed = True
|
||||
(self.experiment / 'bulk.npy').write_bytes(b'changed after initial seal check')
|
||||
|
||||
with mock.patch.object(publication, '_copy', side_effect=copy_then_change):
|
||||
with self.assertRaises(ValueError):
|
||||
self.publish()
|
||||
self.assertTrue((self.registry / 'publication-failure.json').is_file())
|
||||
self.assertFalse((self.registry / 'publication-receipt.json').exists())
|
||||
|
||||
def test_helper_pin_checked_without_modifying_old_globals_or_cache(self):
|
||||
helper = publication.load_helpers(HELPER)
|
||||
self.assertEqual(helper.MODEL_RELATIVE, Path('models/male-cns-lif/pn-recruitment-audit-v1'))
|
||||
self.assertEqual(helper.FINAL_STATUS, 'PASS-CONDITIONAL-PN-APL-SOURCE-AUDIT')
|
||||
wrong = self.root / 'wrong-helper.py'
|
||||
wrong.write_bytes(b'raise RuntimeError("must never execute")\n')
|
||||
with self.assertRaisesRegex(ValueError, 'helper hash mismatch'):
|
||||
publication.publish(self.root, wrong)
|
||||
self.assertFalse(self.registry.exists())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
"""Synthetic temporary Git repositories only; no real publication or science."""
|
||||
import contextlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import verify_git_payload as verifier
|
||||
|
||||
|
||||
def encoded(value):
|
||||
return (json.dumps(value, sort_keys=True, indent=2) + '\n').encode('utf-8')
|
||||
|
||||
|
||||
def sealed(relative, payload):
|
||||
rows = [dict(path=path, bytes=len(data), sha256=verifier.sha(data)) for path, data in sorted(payload.items())]
|
||||
manifest = encoded(dict(root=verifier.PROJECT + '/' + relative, files=rows))
|
||||
checksums = ''.join(row['sha256'] + ' ' + row['path'] + '\n' for row in rows)
|
||||
checksums += verifier.sha(manifest) + ' manifest.json\n'
|
||||
controls = {'manifest.json': manifest, 'checksums.sha256': checksums.encode('utf-8')}
|
||||
proof = dict(pass_all=True, root=verifier.PROJECT + '/' + relative,
|
||||
payload_file_count=len(rows), payload_bytes=sum(row['bytes'] for row in rows),
|
||||
manifest_sha256=verifier.sha(manifest), checksums_sha256=verifier.sha(controls['checksums.sha256']),
|
||||
excluded_pycache_files=0, verification_scope='synthetic test only')
|
||||
return rows, controls, proof
|
||||
|
||||
|
||||
@unittest.skipUnless(shutil.which('git'), 'Git executable is required')
|
||||
class GitPayloadTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
temporary = tempfile.TemporaryDirectory(prefix='phase4am-git-payload-synthetic-')
|
||||
self.addCleanup(temporary.cleanup)
|
||||
self.repo = Path(temporary.name).resolve()
|
||||
self.git('init', '--quiet')
|
||||
self.git('config', 'user.name', 'Synthetic Test')
|
||||
self.git('config', 'user.email', 'synthetic@example.invalid')
|
||||
self.git('config', 'core.autocrlf', 'false')
|
||||
self.git('config', 'commit.gpgsign', 'false')
|
||||
self.model = {'module.py': b'# synthetic, not a brain\nx = 1\n', 'config.json': b'{"synthetic": true}\n'}
|
||||
self.exp = {'audit/final-status.json': encoded(dict(status='FAIL-SYNTHETIC-AM-INTERFACE')),
|
||||
'case/summary.json': b'{"synthetic": true}\n', 'bulk.npy': b'not-a-real-array\x00'}
|
||||
self.build()
|
||||
|
||||
def git(self, *args, data=None):
|
||||
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)
|
||||
result = subprocess.run(['git', '-C', str(self.repo), *args], input=data,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, check=False)
|
||||
self.assertEqual(result.returncode, 0, result.stderr.decode('utf-8', 'replace'))
|
||||
return result.stdout
|
||||
|
||||
def write(self, path, data):
|
||||
target = self.repo / path
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_bytes(data)
|
||||
|
||||
def build(self):
|
||||
_, model_controls, model_proof = sealed(verifier.MODEL, self.model)
|
||||
exp_rows, exp_controls, exp_proof = sealed(verifier.EXPERIMENT, self.exp)
|
||||
for path, data in {**self.model, **model_controls}.items():
|
||||
self.write(verifier.MODEL + '/' + path, data)
|
||||
registry = {}
|
||||
index = []
|
||||
omitted = []
|
||||
for path, data in {**self.exp, **exp_controls}.items():
|
||||
row = dict(path=path, bytes=len(data), sha256=verifier.sha(data))
|
||||
if Path(path).suffix in verifier.SUFFIXES and len(data) <= verifier.MAX_EVIDENCE:
|
||||
registry['evidence/' + path] = data
|
||||
index.append(dict(source=verifier.EXPERIMENT + '/' + path, copy='evidence/' + path,
|
||||
bytes=len(data), sha256=verifier.sha(data)))
|
||||
else:
|
||||
omitted.append(dict(row, reason='bulk_or_non_evidence_extension'))
|
||||
for path, data in model_controls.items():
|
||||
registry['model/' + path] = data
|
||||
index.append(dict(source=verifier.MODEL + '/' + path, copy='model/' + path,
|
||||
bytes=len(data), sha256=verifier.sha(data)))
|
||||
registry['evidence-index.json'] = encoded(index)
|
||||
registry['seal-verification.json'] = encoded(dict(model=model_proof, experiment=exp_proof))
|
||||
self.receipt = dict(publication_complete=True, registry=verifier.REGISTRY, project_root=verifier.PROJECT,
|
||||
model_payload_remains_at=verifier.MODEL, bulk_remains_at=verifier.EXPERIMENT,
|
||||
model_manifest_sha256=model_proof['manifest_sha256'],
|
||||
experiment_manifest_sha256=exp_proof['manifest_sha256'],
|
||||
copied_evidence_files=len(index), copied_evidence_bytes=sum(row['bytes'] for row in index),
|
||||
files=[dict(path=path, bytes=len(data), sha256=verifier.sha(data)) for path, data in sorted(registry.items())],
|
||||
experiment_payload_not_copied=omitted, declared_audit_status='FAIL-SYNTHETIC-AM-INTERFACE')
|
||||
for path, data in registry.items():
|
||||
self.write(verifier.REGISTRY + '/' + path, data)
|
||||
self.receipt_write()
|
||||
self.git('add', '--', verifier.MODEL, verifier.REGISTRY)
|
||||
|
||||
def receipt_write(self):
|
||||
data = encoded(self.receipt)
|
||||
self.write(verifier.REGISTRY + '/publication-receipt.json', data)
|
||||
self.anchor = verifier.sha(data)
|
||||
|
||||
def anchored_registry_change(self, relative, data):
|
||||
self.write(verifier.REGISTRY + '/' + relative, data)
|
||||
row = next(row for row in self.receipt['files'] if row['path'] == relative)
|
||||
row.update(bytes=len(data), sha256=verifier.sha(data))
|
||||
self.receipt_write()
|
||||
self.git('add', '--', verifier.REGISTRY)
|
||||
|
||||
def verify(self, **kwargs):
|
||||
return verifier.verify_git_payload(self.repo, self.anchor, **({'staged': True} if not kwargs else kwargs))
|
||||
|
||||
def test_staged_verifies_blob_bytes_and_leaves_index_unchanged(self):
|
||||
before = (self.repo / '.git/index').read_bytes()
|
||||
result = self.verify()
|
||||
self.assertTrue(result['pass_all'])
|
||||
self.assertEqual(result['model_payload_files'], 2)
|
||||
self.assertEqual(result['declared_audit_status'], 'FAIL-SYNTHETIC-AM-INTERFACE')
|
||||
self.assertFalse(result['experiment_bulk_verified_by_this_tool'])
|
||||
self.assertFalse(result['scientific_gate_executed'])
|
||||
self.assertTrue(result['publication_integrity_is_not_scientific_pass'])
|
||||
self.assertEqual(before, (self.repo / '.git/index').read_bytes())
|
||||
self.assertFalse((self.repo / verifier.EXPERIMENT).exists())
|
||||
|
||||
def test_dirty_or_missing_worktree_payload_does_not_replace_good_index(self):
|
||||
(self.repo / verifier.MODEL / 'module.py').write_bytes(b'working copy differs')
|
||||
(self.repo / verifier.REGISTRY / 'publication-receipt.json').unlink()
|
||||
self.assertTrue(self.verify()['pass_all'])
|
||||
|
||||
def test_correct_worktree_cannot_hide_corrupt_staged_blob(self):
|
||||
relative = verifier.MODEL + '/module.py'
|
||||
self.write(relative, self.model['module.py'].replace(b'x = 1', b'x = 2'))
|
||||
self.git('add', '--', relative)
|
||||
self.write(relative, self.model['module.py'])
|
||||
with self.assertRaisesRegex(ValueError, 'Git blob SHA-256 mismatch'):
|
||||
self.verify()
|
||||
|
||||
def test_commit_verifies_immutable_commit_not_current_index(self):
|
||||
self.git('commit', '--quiet', '-m', 'synthetic only')
|
||||
commit = self.git('rev-parse', 'HEAD').decode().strip()
|
||||
self.write(verifier.MODEL + '/module.py', b'bad newer index')
|
||||
self.git('add', '--', verifier.MODEL)
|
||||
result = self.verify(commit='HEAD')
|
||||
self.assertEqual(result['commit'], commit)
|
||||
self.assertEqual(result['source'], 'commit')
|
||||
self.assertTrue(result['pass_all'])
|
||||
|
||||
def test_external_receipt_anchor_is_mandatory_and_checked(self):
|
||||
for anchor in (None, '', '0' * 64, self.anchor.upper()):
|
||||
with self.subTest(anchor=anchor), self.assertRaises(ValueError):
|
||||
verifier.verify_git_payload(self.repo, anchor, staged=True)
|
||||
|
||||
def test_extra_model_file_is_rejected_but_outside_scope_is_ignored(self):
|
||||
self.write('unrelated/old-file.txt', b'outside audit scope')
|
||||
self.git('add', '--', 'unrelated')
|
||||
self.assertTrue(self.verify()['pass_all'])
|
||||
self.write(verifier.MODEL + '/extra.py', b'extra')
|
||||
self.git('add', '--', verifier.MODEL)
|
||||
with self.assertRaisesRegex(ValueError, 'Model Git inventory'):
|
||||
self.verify()
|
||||
|
||||
def test_extra_registry_file_is_rejected(self):
|
||||
self.write(verifier.REGISTRY + '/unlisted.json', b'{}')
|
||||
self.git('add', '--', verifier.REGISTRY)
|
||||
with self.assertRaisesRegex(ValueError, 'Registry Git inventory'):
|
||||
self.verify()
|
||||
|
||||
def test_missing_staged_model_file_is_rejected(self):
|
||||
self.git('update-index', '--force-remove', verifier.MODEL + '/module.py')
|
||||
with self.assertRaisesRegex(ValueError, 'Model Git inventory'):
|
||||
self.verify()
|
||||
|
||||
def test_symlink_blob_mode_is_rejected_without_following_it(self):
|
||||
path = verifier.MODEL + '/module.py'
|
||||
oid = self.git('rev-parse', ':' + path).decode().strip()
|
||||
self.git('update-index', '--cacheinfo', '120000,' + oid + ',' + path)
|
||||
with self.assertRaisesRegex(ValueError, 'Symlink/gitlink/nonregular'):
|
||||
self.verify()
|
||||
|
||||
def test_gitlink_is_rejected(self):
|
||||
self.git('commit', '--quiet', '-m', 'synthetic only')
|
||||
oid = self.git('rev-parse', 'HEAD').decode().strip()
|
||||
self.git('update-index', '--add', '--cacheinfo', '160000,' + oid + ',' + verifier.MODEL + '/submodule')
|
||||
with self.assertRaisesRegex(ValueError, 'Symlink/gitlink/nonregular'):
|
||||
self.verify()
|
||||
|
||||
def test_unmerged_index_is_rejected(self):
|
||||
path = verifier.MODEL + '/module.py'
|
||||
oid = self.git('rev-parse', ':' + path).decode().strip()
|
||||
self.git('update-index', '--force-remove', path)
|
||||
self.git('update-index', '--index-info', data=('100644 ' + oid + ' 1\t' + path + '\n').encode())
|
||||
with self.assertRaisesRegex(ValueError, 'Unmerged index'):
|
||||
self.verify()
|
||||
|
||||
def test_anchored_index_source_escape_is_rejected(self):
|
||||
data = json.loads((self.repo / verifier.REGISTRY / 'evidence-index.json').read_bytes())
|
||||
data[0]['source'] = 'experiments/another-phase/audit/final-status.json'
|
||||
self.anchored_registry_change('evidence-index.json', encoded(data))
|
||||
with self.assertRaisesRegex(ValueError, 'Evidence index differs'):
|
||||
self.verify()
|
||||
|
||||
def test_anchored_registry_copy_cannot_disagree_with_model(self):
|
||||
data = (self.repo / verifier.REGISTRY / 'model/checksums.sha256').read_bytes()
|
||||
self.anchored_registry_change('model/checksums.sha256', b'0' * 64 + data[64:])
|
||||
with self.assertRaisesRegex(ValueError, 'Checksum inventory'):
|
||||
self.verify()
|
||||
|
||||
def test_anchored_receipt_cannot_change_model_manifest_anchor(self):
|
||||
self.receipt['model_manifest_sha256'] = '0' * 64
|
||||
self.receipt_write(); self.git('add', '--', verifier.REGISTRY)
|
||||
with self.assertRaisesRegex(ValueError, 'Receipt-bound manifest'):
|
||||
self.verify()
|
||||
|
||||
def test_registry_payload_corruption_is_rejected(self):
|
||||
path = verifier.REGISTRY + '/evidence/case/summary.json'
|
||||
self.write(path, b'{"synthetic":false}\n')
|
||||
self.git('add', '--', path)
|
||||
with self.assertRaisesRegex(ValueError, 'Git blob (byte count|SHA-256) mismatch'):
|
||||
self.verify()
|
||||
|
||||
def test_anchored_receipt_path_escape_and_duplicate_fail(self):
|
||||
self.receipt['files'][0]['path'] = '../outside.json'
|
||||
self.receipt_write(); self.git('add', '--', verifier.REGISTRY)
|
||||
with self.assertRaisesRegex(ValueError, 'Noncanonical or escaping path'):
|
||||
self.verify()
|
||||
|
||||
def test_duplicate_json_key_fails_even_with_external_anchor(self):
|
||||
data = encoded(self.receipt).replace(b'{\n', b'{\n "publication_complete": true,\n', 1)
|
||||
self.write(verifier.REGISTRY + '/publication-receipt.json', data)
|
||||
self.anchor = verifier.sha(data)
|
||||
self.git('add', '--', verifier.REGISTRY)
|
||||
with self.assertRaisesRegex(ValueError, 'Duplicate JSON key'):
|
||||
self.verify()
|
||||
|
||||
def test_incomplete_status_is_preserved_not_regraded(self):
|
||||
self.receipt['declared_audit_status'] = 'INCOMPLETE-SYNTHETIC-AM-EXECUTION'
|
||||
self.receipt_write(); self.git('add', '--', verifier.REGISTRY)
|
||||
result = self.verify()
|
||||
self.assertEqual(result['declared_audit_status'], 'INCOMPLETE-SYNTHETIC-AM-EXECUTION')
|
||||
self.assertTrue(result['publication_integrity_is_not_scientific_pass'])
|
||||
|
||||
def test_anchored_bulk_omission_inventory_mismatch_fails(self):
|
||||
self.receipt['experiment_payload_not_copied'] = []
|
||||
self.receipt_write(); self.git('add', '--', verifier.REGISTRY)
|
||||
with self.assertRaisesRegex(ValueError, 'omitted-bulk inventory mismatch'):
|
||||
self.verify()
|
||||
|
||||
def test_anchored_seal_proof_contradiction_fails(self):
|
||||
proof = json.loads((self.repo / verifier.REGISTRY / 'seal-verification.json').read_bytes())
|
||||
proof['model']['payload_file_count'] += 1
|
||||
self.anchored_registry_change('seal-verification.json', encoded(proof))
|
||||
with self.assertRaisesRegex(ValueError, 'Seal-verification evidence contradicts'):
|
||||
self.verify()
|
||||
|
||||
def test_both_or_neither_git_sources_fail(self):
|
||||
for kwargs in ({}, {'staged': True, 'commit': 'HEAD'}):
|
||||
with self.subTest(kwargs=kwargs), self.assertRaisesRegex(ValueError, 'Select exactly one'):
|
||||
verifier.verify_git_payload(self.repo, self.anchor, **kwargs)
|
||||
|
||||
def test_cli_success_and_failure_are_json_with_exit_status(self):
|
||||
for anchor, expected in ((self.anchor, 0), ('0' * 64, 1)):
|
||||
with self.subTest(expected=expected), contextlib.redirect_stdout(io.StringIO()) as output:
|
||||
code = verifier.main(['--repo', str(self.repo), '--staged', '--receipt-sha256', anchor])
|
||||
result = json.loads(output.getvalue())
|
||||
self.assertEqual(code, expected)
|
||||
self.assertEqual(result['pass_all'], expected == 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
"""Verify fixed Phase 4AM Git blobs, not working-tree files or scientific gates.
|
||||
|
||||
The publication receipt SHA-256 must come from an external trusted record.
|
||||
Only the index or one resolved commit is read. No publish, checkout, index
|
||||
mutation, network fetch, or scientific execution is performed.
|
||||
"""
|
||||
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/wholebrain-plastic-interface-v1'
|
||||
EXPERIMENT = 'experiments/phase4am-wholebrain-plastic-interface'
|
||||
REGISTRY = 'registry/phase4am-wholebrain-plastic-interface'
|
||||
CONTROLS = frozenset(('manifest.json', 'checksums.sha256'))
|
||||
SHA256 = re.compile(r'[0-9a-f]{64}\Z')
|
||||
OID = re.compile(r'(?:[0-9a-f]{40}|[0-9a-f]{64})\Z')
|
||||
MAX_EVIDENCE = 4 * 1024 ** 2
|
||||
MAX_BLOB = 64 * 1024 ** 2
|
||||
SUFFIXES = frozenset(('.json', '.csv', '.md', '.log', '.sha256'))
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def sha(data):
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def relative(value):
|
||||
require(isinstance(value, str) and bool(value) and
|
||||
not any(char in value for char in '\\\x00\n\r\t'), 'Invalid relative path')
|
||||
path = PurePosixPath(value)
|
||||
require(not path.is_absolute() and str(path) == value and
|
||||
all(part not in ('', '.', '..') and ':' not in part for part in path.parts),
|
||||
'Noncanonical or escaping path: ' + value)
|
||||
return value
|
||||
|
||||
|
||||
def unique_object(pairs):
|
||||
value = {}
|
||||
for key, item in pairs:
|
||||
require(key not in value, 'Duplicate JSON key: ' + key)
|
||||
value[key] = item
|
||||
return value
|
||||
|
||||
|
||||
def json_bytes(data):
|
||||
def nonfinite(value):
|
||||
raise ValueError('Nonfinite JSON value: ' + value)
|
||||
return json.loads(data.decode('utf-8'), object_pairs_hook=unique_object,
|
||||
parse_constant=nonfinite)
|
||||
|
||||
|
||||
def rows_by_path(rows, label):
|
||||
require(isinstance(rows, list) and bool(rows), 'Missing/empty ' + label)
|
||||
result = {}
|
||||
for row in rows:
|
||||
require(isinstance(row, dict) and set(row) == {'path', 'bytes', 'sha256'},
|
||||
'Invalid ' + label + ' row schema')
|
||||
path = relative(row['path'])
|
||||
require(path not in result and '__pycache__' not in PurePosixPath(path).parts,
|
||||
'Duplicate/cache ' + label + ' path: ' + path)
|
||||
require(type(row['bytes']) is int and row['bytes'] >= 0 and
|
||||
isinstance(row['sha256'], str) and SHA256.fullmatch(row['sha256']),
|
||||
'Invalid bytes/SHA-256 in ' + label + ': ' + path)
|
||||
result[path] = row
|
||||
return result
|
||||
|
||||
|
||||
class GitBlobs:
|
||||
"""Snapshot inventory plus immutable object reads; no work-tree fallback."""
|
||||
|
||||
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)
|
||||
require(self.repo.is_dir(), 'Repository must be a directory')
|
||||
self.env = os.environ.copy()
|
||||
# The explicitly supplied repo/index must not be redirected by caller env.
|
||||
for key in ('GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_COMMON_DIR',
|
||||
'GIT_OBJECT_DIRECTORY', 'GIT_ALTERNATE_OBJECT_DIRECTORIES'):
|
||||
self.env.pop(key, None)
|
||||
self.env.update(GIT_OPTIONAL_LOCKS='0', GIT_NO_REPLACE_OBJECTS='1', GIT_NO_LAZY_FETCH='1')
|
||||
self.base = ['git', '--no-optional-locks', '-c', 'core.fsmonitor=false',
|
||||
'-c', 'gc.auto=0', '-C', str(self.repo)]
|
||||
top = self.command('rev-parse', '--show-toplevel').decode('utf-8').strip()
|
||||
require(Path(top).resolve(strict=True) == self.repo, 'Supply the Git repository top-level directory')
|
||||
if staged:
|
||||
self.commit = None
|
||||
else:
|
||||
require(isinstance(commit, str) and bool(commit) and not commit.startswith('-') and
|
||||
not any(c in commit for c in '\x00\n\r'), 'Invalid commit selector')
|
||||
self.commit = self.command('rev-parse', '--verify', '--end-of-options',
|
||||
commit + '^{commit}').decode('ascii').strip()
|
||||
require(OID.fullmatch(self.commit), 'Commit did not resolve to a full object ID')
|
||||
self.staged = staged
|
||||
self.inventory_bytes = self.inventory()
|
||||
self.entries = self.parse_inventory(self.inventory_bytes)
|
||||
self.verified = {}
|
||||
|
||||
def command(self, *args):
|
||||
result = subprocess.run(self.base + list(args), env=self.env, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, check=False)
|
||||
require(result.returncode == 0, 'Git read failed: ' + result.stderr.decode('utf-8', 'replace').strip())
|
||||
return result.stdout
|
||||
|
||||
def inventory(self):
|
||||
if self.staged:
|
||||
return self.command('ls-files', '--stage', '--full-name', '-z', '--', MODEL, REGISTRY)
|
||||
return self.command('ls-tree', '-r', '-z', '--full-tree', self.commit, '--', MODEL, REGISTRY)
|
||||
|
||||
def parse_inventory(self, data):
|
||||
result = {}
|
||||
require(data.endswith(b'\x00'), 'Empty/malformed scoped Git inventory')
|
||||
for entry in data[:-1].split(b'\x00'):
|
||||
metadata, raw_path = entry.split(b'\t', 1)
|
||||
fields = metadata.decode('ascii').split(' ')
|
||||
require(len(fields) == 3, 'Malformed Git entry')
|
||||
if self.staged:
|
||||
mode, oid, stage = fields
|
||||
require(stage == '0', 'Unmerged index entry is forbidden')
|
||||
else:
|
||||
mode, kind, oid = fields
|
||||
require(kind == 'blob', 'Non-blob Git entry is forbidden')
|
||||
path = relative(raw_path.decode('utf-8'))
|
||||
require(path.startswith(MODEL + '/') or path.startswith(REGISTRY + '/'),
|
||||
'Git entry escapes the fixed AM scopes: ' + path)
|
||||
require(mode in ('100644', '100755') and OID.fullmatch(oid),
|
||||
'Symlink/gitlink/nonregular Git entry is forbidden: ' + path)
|
||||
require(path not in result, 'Duplicate Git path: ' + path)
|
||||
result[path] = dict(mode=mode, oid=oid)
|
||||
return result
|
||||
|
||||
def read(self, path):
|
||||
relative(path)
|
||||
require(path in self.entries, 'Missing Git blob: ' + path)
|
||||
oid = self.entries[path]['oid']
|
||||
size = int(self.command('cat-file', '-s', oid).decode('ascii').strip())
|
||||
require(0 <= size <= MAX_BLOB, 'Git blob exceeds publication verifier limit: ' + path)
|
||||
# cat-file blob addresses the object, so attributes/filters/autocrlf do
|
||||
# not turn a corrupt staged blob into a good working-tree substitute.
|
||||
data = self.command('cat-file', 'blob', oid)
|
||||
require(len(data) == size, 'Git object size changed: ' + path)
|
||||
self.verified[path] = dict(path=path, bytes=len(data), sha256=sha(data),
|
||||
git_oid=oid, git_mode=self.entries[path]['mode'])
|
||||
return data
|
||||
|
||||
def match(self, path, row):
|
||||
data = self.read(path)
|
||||
require(len(data) == row['bytes'], 'Git blob byte count mismatch: ' + path)
|
||||
require(sha(data) == row['sha256'], 'Git blob SHA-256 mismatch: ' + path)
|
||||
return data
|
||||
|
||||
def finish(self):
|
||||
require(self.inventory() == self.inventory_bytes, 'Scoped Git inventory changed during verification')
|
||||
|
||||
|
||||
def seal_controls(manifest_data, checksum_data, expected_relative, expected_sha):
|
||||
"""Validate control syntax/inventory; does not claim absent bulk was read."""
|
||||
require(sha(manifest_data) == expected_sha, 'Receipt-bound manifest SHA-256 mismatch')
|
||||
manifest = json_bytes(manifest_data)
|
||||
require(isinstance(manifest, dict) and set(manifest) == {'root', 'files'} and
|
||||
manifest['root'] == PROJECT + '/' + expected_relative, 'Wrong seal manifest schema/root')
|
||||
rows = rows_by_path(manifest['files'], 'manifest')
|
||||
require(not (set(rows) & CONTROLS), 'Seal controls listed as payload')
|
||||
checksums = {}
|
||||
for line in checksum_data.decode('utf-8').splitlines():
|
||||
require(len(line) >= 67 and line[64:66] == ' ' and SHA256.fullmatch(line[:64]),
|
||||
'Malformed checksum line')
|
||||
path = relative(line[66:])
|
||||
require(path not in checksums, 'Duplicate checksum path: ' + path)
|
||||
checksums[path] = line[:64]
|
||||
require(checksums == {**{name: row['sha256'] for name, row in rows.items()},
|
||||
'manifest.json': sha(manifest_data)},
|
||||
'Checksum inventory or manifest self-checksum mismatch')
|
||||
return rows
|
||||
|
||||
|
||||
def control_rows(manifest_data, checksum_data):
|
||||
return {name: dict(path=name, bytes=len(data), sha256=sha(data)) for name, data in
|
||||
(('manifest.json', manifest_data), ('checksums.sha256', checksum_data))}
|
||||
|
||||
|
||||
def verify_git_payload(repo, receipt_sha256, *, staged=False, commit=None):
|
||||
require(isinstance(receipt_sha256, str) and SHA256.fullmatch(receipt_sha256),
|
||||
'External publication receipt SHA-256 is required')
|
||||
blobs = GitBlobs(repo, staged=staged, commit=commit)
|
||||
receipt_data = blobs.read(REGISTRY + '/publication-receipt.json')
|
||||
require(len(receipt_data) <= MAX_EVIDENCE and sha(receipt_data) == receipt_sha256,
|
||||
'External publication receipt SHA-256 mismatch')
|
||||
receipt = json_bytes(receipt_data)
|
||||
require(isinstance(receipt, dict) and receipt.get('publication_complete') is True and
|
||||
receipt.get('registry') == REGISTRY and receipt.get('project_root') == PROJECT and
|
||||
receipt.get('model_payload_remains_at') == MODEL and receipt.get('bulk_remains_at') == EXPERIMENT,
|
||||
'Wrong/incomplete AM publication receipt or fixed scope')
|
||||
for key in ('model_manifest_sha256', 'experiment_manifest_sha256'):
|
||||
require(isinstance(receipt.get(key), str) and SHA256.fullmatch(receipt[key]),
|
||||
'Invalid receipt manifest anchor: ' + key)
|
||||
registry_rows = rows_by_path(receipt.get('files'), 'receipt files')
|
||||
require('publication-receipt.json' not in registry_rows, 'Receipt must not list itself')
|
||||
require({'evidence-index.json', 'seal-verification.json', 'model/manifest.json',
|
||||
'model/checksums.sha256', 'evidence/manifest.json', 'evidence/checksums.sha256'} <= set(registry_rows),
|
||||
'Required registry evidence/control missing')
|
||||
observed_registry = {path[len(REGISTRY) + 1:] for path in blobs.entries if path.startswith(REGISTRY + '/')}
|
||||
require(observed_registry == set(registry_rows) | {'publication-receipt.json'},
|
||||
'Registry Git inventory has unlisted/missing files: ' + repr(sorted(
|
||||
observed_registry ^ (set(registry_rows) | {'publication-receipt.json'}))))
|
||||
controls_data = {}
|
||||
for path, row in registry_rows.items():
|
||||
require(row['bytes'] <= MAX_EVIDENCE, 'Registry receipt file exceeds 4 MiB')
|
||||
data = blobs.match(REGISTRY + '/' + path, row)
|
||||
if path in ('evidence-index.json', 'seal-verification.json', 'model/manifest.json',
|
||||
'model/checksums.sha256', 'evidence/manifest.json', 'evidence/checksums.sha256'):
|
||||
controls_data[path] = data
|
||||
model_manifest, model_checksums = (controls_data['model/' + name] for name in ('manifest.json', 'checksums.sha256'))
|
||||
model_rows = seal_controls(model_manifest, model_checksums, MODEL, receipt['model_manifest_sha256'])
|
||||
model_controls = control_rows(model_manifest, model_checksums)
|
||||
observed_model = {path[len(MODEL) + 1:] for path in blobs.entries if path.startswith(MODEL + '/')}
|
||||
require(observed_model == set(model_rows) | CONTROLS,
|
||||
'Model Git inventory has unlisted/missing files: ' + repr(sorted(observed_model ^ (set(model_rows) | CONTROLS))))
|
||||
for path, row in {**model_rows, **model_controls}.items():
|
||||
blobs.match(MODEL + '/' + path, row)
|
||||
exp_manifest, exp_checksums = (controls_data['evidence/' + name] for name in ('manifest.json', 'checksums.sha256'))
|
||||
exp_rows = seal_controls(exp_manifest, exp_checksums, EXPERIMENT, receipt['experiment_manifest_sha256'])
|
||||
exp_controls = control_rows(exp_manifest, exp_checksums)
|
||||
expected_index = {}
|
||||
omitted = []
|
||||
for path, row in {**exp_rows, **exp_controls}.items():
|
||||
if PurePosixPath(path).suffix.lower() in SUFFIXES and row['bytes'] <= MAX_EVIDENCE:
|
||||
expected_index['evidence/' + path] = dict(source=EXPERIMENT + '/' + path,
|
||||
copy='evidence/' + path, bytes=row['bytes'], sha256=row['sha256'])
|
||||
else:
|
||||
omitted.append(dict(row, reason='exceeds_4_MiB' if row['bytes'] > MAX_EVIDENCE
|
||||
else 'bulk_or_non_evidence_extension'))
|
||||
for path, row in model_controls.items():
|
||||
expected_index['model/' + path] = dict(source=MODEL + '/' + path,
|
||||
copy='model/' + path, bytes=row['bytes'], sha256=row['sha256'])
|
||||
index = json_bytes(controls_data['evidence-index.json'])
|
||||
require(isinstance(index, list), 'Evidence index must be a list')
|
||||
actual_index = {}
|
||||
for row in index:
|
||||
require(isinstance(row, dict) and set(row) == {'source', 'copy', 'bytes', 'sha256'},
|
||||
'Invalid evidence-index row schema')
|
||||
path = relative(row['copy']); relative(row['source'])
|
||||
require(path not in actual_index and type(row['bytes']) is int and row['bytes'] >= 0 and
|
||||
isinstance(row['sha256'], str) and SHA256.fullmatch(row['sha256']),
|
||||
'Invalid/duplicate evidence-index row')
|
||||
actual_index[path] = row
|
||||
require(actual_index == expected_index, 'Evidence index differs from receipt-bound seals/selection policy')
|
||||
require(set(registry_rows) == set(expected_index) | {'evidence-index.json', 'seal-verification.json'},
|
||||
'Receipt files disagree with copied evidence index')
|
||||
for path, row in expected_index.items():
|
||||
require(registry_rows[path] == dict(path=path, bytes=row['bytes'], sha256=row['sha256']),
|
||||
'Evidence-index SHA/bytes disagree with receipt files: ' + path)
|
||||
require(type(receipt.get('copied_evidence_files')) is int and
|
||||
receipt['copied_evidence_files'] == len(expected_index) and
|
||||
type(receipt.get('copied_evidence_bytes')) is int and
|
||||
receipt['copied_evidence_bytes'] == sum(row['bytes'] for row in expected_index.values()),
|
||||
'Receipt copied-evidence totals mismatch')
|
||||
omitted_receipt = receipt.get('experiment_payload_not_copied')
|
||||
require(isinstance(omitted_receipt, list) and all(isinstance(row, dict) for row in omitted_receipt) and
|
||||
sorted(omitted_receipt, key=lambda row: row.get('path', '')) == sorted(omitted, key=lambda row: row['path']),
|
||||
'Receipt omitted-bulk inventory mismatch')
|
||||
proof = json_bytes(controls_data['seal-verification.json'])
|
||||
require(isinstance(proof, dict) and set(proof) == {'model', 'experiment'}, 'Invalid seal-verification evidence')
|
||||
for name, prefix, payload, controls in (('model', MODEL, model_rows, model_controls),
|
||||
('experiment', EXPERIMENT, exp_rows, exp_controls)):
|
||||
record = proof[name]
|
||||
require(isinstance(record, dict) and record.get('pass_all') is True and
|
||||
record.get('root') == PROJECT + '/' + prefix and
|
||||
record.get('payload_file_count') == len(payload) and
|
||||
record.get('payload_bytes') == sum(row['bytes'] for row in payload.values()) and
|
||||
record.get('manifest_sha256') == controls['manifest.json']['sha256'] and
|
||||
record.get('checksums_sha256') == controls['checksums.sha256']['sha256'],
|
||||
'Seal-verification evidence contradicts receipt-bound ' + name + ' controls')
|
||||
blobs.finish()
|
||||
return dict(pass_all=True, verification='Phase 4AM actual Git blob publication integrity',
|
||||
source='staged-index' if staged else 'commit', commit=blobs.commit,
|
||||
fixed_scopes=[MODEL, REGISTRY], receipt_sha256=receipt_sha256,
|
||||
model_manifest_sha256=receipt['model_manifest_sha256'],
|
||||
experiment_manifest_sha256=receipt['experiment_manifest_sha256'],
|
||||
declared_audit_status=receipt.get('declared_audit_status'),
|
||||
model_payload_files=len(model_rows), registry_receipt_files=len(registry_rows),
|
||||
verified_blob_count=len(blobs.verified), verified_bytes=sum(row['bytes'] for row in blobs.verified.values()),
|
||||
scoped_inventory_sha256=sha(blobs.inventory_bytes),
|
||||
verified_blobs=[blobs.verified[path] for path in sorted(blobs.verified)],
|
||||
working_tree_payload_used=False, git_index_modified=False,
|
||||
experiment_bulk_verified_by_this_tool=False, scientific_gate_executed=False,
|
||||
publication_integrity_is_not_scientific_pass=True)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--repo', required=True, help='Git repository top-level directory')
|
||||
parser.add_argument('--receipt-sha256', required=True, help='Trusted external publication-receipt.json SHA-256')
|
||||
choice = parser.add_mutually_exclusive_group(required=True)
|
||||
choice.add_argument('--staged', action='store_true', help='Verify the actual stage-0 index blobs')
|
||||
choice.add_argument('--commit', help='Resolve and verify this commit without checking it out')
|
||||
args = parser.parse_args(argv)
|
||||
try:
|
||||
result = verify_git_payload(args.repo, args.receipt_sha256, staged=args.staged, commit=args.commit)
|
||||
except (ValueError, OSError, UnicodeError) as error:
|
||||
result = dict(pass_all=False, verification='Phase 4AM actual Git blob publication integrity',
|
||||
error_type=type(error).__name__, error=str(error), scientific_gate_executed=False,
|
||||
publication_integrity_is_not_scientific_pass=True)
|
||||
print(json.dumps(result, ensure_ascii=False, allow_nan=False))
|
||||
return 1
|
||||
print(json.dumps(result, ensure_ascii=False, allow_nan=False))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
Reference in New Issue
Block a user