Seal Phase 4AN exact continuation engineering evidence
This commit is contained in:
@@ -0,0 +1,335 @@
|
||||
"""Fetch exactly the externally pinned AN registry, never remote brain bulk.
|
||||
|
||||
No network at import. ``fetch`` accepts an authenticated read-only SFTP client;
|
||||
the CLI requires existing host trust and environment-only credentials. Local
|
||||
paths preserve registry relative names. A partial destination is not resumed.
|
||||
This checks selected bytes and publication cross-links, not scientific gates
|
||||
or the untransferred experiment/model payload.
|
||||
"""
|
||||
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'
|
||||
EXP_REL = 'experiments/phase4an-engineering-validation'
|
||||
MODEL_REL = 'models/male-cns-lif/olfactory-conditioning-lab-v1'
|
||||
REGISTRY_REL = 'registry/phase4an-engineering-validation'
|
||||
REGISTRY_ROOT = REMOTE_ROOT + '/' + REGISTRY_REL
|
||||
DEFAULT_DESTINATION = Path(__file__).resolve().parents[2] / 'reports/phase4an-engineering-validation'
|
||||
PREREG_SHA = '7317441126f191ccc6e6d28438870b3921537ebe282a6acb163716a690057945'
|
||||
MAX_FILE_BYTES = 4 * 1024**2
|
||||
MAX_TOTAL_BYTES = 1024**3
|
||||
SUFFIXES = frozenset(('.json', '.csv', '.md', '.log', '.sha256'))
|
||||
MODULES = ('runtime_light', 'runtime_continuation', 'engineering_inputs', 'engineering_plan',
|
||||
'engineering_compare', 'engineering_queue_audit', 'engineering_prepare',
|
||||
'engineering_workflow', 'engineering_scopes', 'engineering_analysis')
|
||||
SOURCE_FILES = tuple(sorted([m + '.py' for m in MODULES] + ['test_' + m + '.py' for m in MODULES] + ['ENGINEERING_GATE_DRAFT.md']))
|
||||
GENERATED = frozenset(('seal-verification.json', 'evidence-index.json', 'source-identity.json'))
|
||||
CONTROLS = ('manifest.json', 'checksums.sha256')
|
||||
PASS = 'PASS-EXACT-LIGHT-CONTINUATION-ENGINEERING-NOT-LEARNING'
|
||||
STATUSES = frozenset((PASS, 'FAIL-NUMERICAL', 'INCOMPLETE-COVERAGE'))
|
||||
SHA = re.compile('[0-9a-f]{64}\\Z')
|
||||
|
||||
|
||||
def require(ok, message):
|
||||
if not ok:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def sha(data):
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def json_bytes(data):
|
||||
def unique(pairs):
|
||||
out = {}
|
||||
for key, value in pairs:
|
||||
require(key not in out, 'Duplicate JSON key')
|
||||
out[key] = value
|
||||
return out
|
||||
def bad(value):
|
||||
raise ValueError('Nonfinite JSON constant')
|
||||
return json.loads(data.decode('utf-8'), object_pairs_hook=unique, parse_constant=bad)
|
||||
|
||||
|
||||
def relative_name(value):
|
||||
require(type(value) is str and value and not any(ord(c) < 32 or c in '\\:"<>|?*' for c in value), 'Invalid relative filename')
|
||||
path = PurePosixPath(value)
|
||||
require(not path.is_absolute() and path.as_posix() == value and
|
||||
all(x not in ('.', '..', '__pycache__') and not x.endswith((' ', '.')) for x in path.parts), 'Noncanonical relative filename')
|
||||
devices = {'CON', 'PRN', 'AUX', 'NUL', *(prefix + number for prefix in ('COM', 'LPT') for number in '123456789¹²³')}
|
||||
require(all(x.split('.')[0].upper() not in devices for x in path.parts), 'Reserved local filename')
|
||||
return path
|
||||
|
||||
|
||||
def file_row(row, small=False):
|
||||
require(type(row) is dict and set(row) == {'path', 'bytes', 'sha256'}, 'Unexpected file row')
|
||||
relative_name(row['path'])
|
||||
require(type(row['bytes']) is int and row['bytes'] >= 0 and type(row['sha256']) is str and SHA.fullmatch(row['sha256']), 'Invalid size/SHA')
|
||||
require(not small or row['bytes'] <= MAX_FILE_BYTES, 'File exceeds 4 MiB')
|
||||
return row
|
||||
|
||||
|
||||
def _collision_check(names):
|
||||
folded = [relative_name(name).as_posix().casefold() for name in names]
|
||||
values = set(folded)
|
||||
require(len(values) == len(folded) and not any(parent.as_posix().casefold() in values
|
||||
for name in names for parent in PurePosixPath(name).parents if str(parent) != '.'), 'Local case/prefix path collision')
|
||||
|
||||
|
||||
def selected(row):
|
||||
path = PurePosixPath(row['path'])
|
||||
if path.parts[0] == 'extension-build': return None, 'build_artifact'
|
||||
if row['bytes'] > MAX_FILE_BYTES: return None, 'exceeds_4_MiB'
|
||||
if path.parts[:2] == ('protocol', 'source-snapshot') and len(path.parts) == 3 and path.name in SOURCE_FILES:
|
||||
return 'source-snapshot/' + path.name, None
|
||||
if path.suffix.lower() not in SUFFIXES: return None, 'bulk_binary_or_unselected_suffix_including_jsonl'
|
||||
return 'evidence/' + row['path'], None
|
||||
|
||||
|
||||
def validate_receipt(raw, expected_sha):
|
||||
require(sha(raw) == expected_sha, 'External receipt SHA mismatch')
|
||||
receipt = json_bytes(raw)
|
||||
require(type(receipt) is dict and receipt.get('schema') == 'phase4an-engineering-publication-v1' and
|
||||
receipt.get('publication_complete') is True and receipt.get('project_root') == REMOTE_ROOT and
|
||||
receipt.get('registry') == REGISTRY_REL and receipt.get('declared_status') in STATUSES, 'Wrong/incomplete publication receipt')
|
||||
for key in ('used_sources_verified_before_and_after', 'complete_checksums_reference_bulk_not_copied',
|
||||
'publication_success_is_not_engineering_or_learning_pass'):
|
||||
require(receipt.get(key) is True, 'Missing publication qualification: ' + key)
|
||||
for key in ('model_directory_sealed', 'model_files_copied', 'complete_bulk_backup', 'numerical_gate_rerun_by_publisher',
|
||||
'training', 'candidate_generated', 'learning_pass_claimed', 'existing_files_modified', 'helper_globals_modified'):
|
||||
require(receipt.get(key) is False, 'Unexpected publication claim: ' + key)
|
||||
require(receipt.get('selection_policy') == dict(max_file_bytes=MAX_FILE_BYTES, suffixes=sorted(SUFFIXES),
|
||||
source_snapshot_names=list(SOURCE_FILES), excluded_tree='extension-build', all_jsonl_excluded=True), 'Selection policy changed')
|
||||
require(receipt.get('used_source_count') == 21 and receipt.get('completed_calls') == 32 and receipt.get('executed_ticks') == 11074 and
|
||||
receipt.get('preregistration_sha256') == PREREG_SHA and receipt.get('source_identity') == 'source-identity.json' and
|
||||
receipt.get('bulk_remains_at') == EXP_REL, 'Receipt authority/scope changed')
|
||||
for key in ('experiment_manifest_sha256', 'experiment_checksums_sha256', 'final_sha256', 'analysis_sha256', 'publisher_sha256', 'helper_sha256'):
|
||||
require(type(receipt.get(key)) is str and SHA.fullmatch(receipt[key]), 'Missing SHA anchor: ' + key)
|
||||
rows = {}
|
||||
require(type(receipt.get('files')) is list and receipt['files'], 'Empty publication inventory')
|
||||
for row in receipt['files']:
|
||||
file_row(row, small=True); name = row['path']
|
||||
allowed = name in GENERATED or name in {'source-snapshot/' + n for n in SOURCE_FILES} or (
|
||||
name.startswith('evidence/') and PurePosixPath(name).suffix.lower() in SUFFIXES and not name.startswith('evidence/extension-build/'))
|
||||
require(allowed and name not in rows, 'Unexpected/duplicate registry file')
|
||||
rows[name] = row
|
||||
required = {*GENERATED, *('source-snapshot/' + n for n in SOURCE_FILES),
|
||||
'evidence/manifest.json', 'evidence/checksums.sha256', 'evidence/protocol/preregistered.json',
|
||||
'evidence/audit/final-status.json', 'evidence/audit/engineering-analysis.json'}
|
||||
require(required <= set(rows), 'Missing required evidence/source/control files')
|
||||
_collision_check([*rows, 'publication-receipt.json', 'fetch-verification.json', 'fetch-failure.json'])
|
||||
return receipt, rows
|
||||
|
||||
|
||||
def validate_manifest(manifest_raw, checksums_raw, receipt):
|
||||
require(sha(manifest_raw) == receipt['experiment_manifest_sha256'] and sha(checksums_raw) == receipt['experiment_checksums_sha256'], 'Manifest/control anchor mismatch')
|
||||
manifest = json_bytes(manifest_raw)
|
||||
require(type(manifest) is dict and set(manifest) == {'root', 'files'} and manifest['root'] == REMOTE_ROOT + '/' + EXP_REL and
|
||||
type(manifest['files']) is list and manifest['files'], 'Wrong experiment manifest')
|
||||
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
|
||||
checks = {}
|
||||
for line in checksums_raw.decode('utf-8').splitlines():
|
||||
require(len(line) >= 67 and line[64:66] == ' ' and SHA.fullmatch(line[:64]), 'Malformed checksum line')
|
||||
name = line[66:]; relative_name(name)
|
||||
require(name not in checks, 'Duplicate checksum path'); checks[name] = line[:64]
|
||||
require(checks == {**{k: row['sha256'] for k, row in rows.items()}, 'manifest.json': receipt['experiment_manifest_sha256']},
|
||||
'Manifest self-SHA/checksum inventory mismatch')
|
||||
return rows
|
||||
|
||||
|
||||
def validate_links(cached, receipt, rows, manifest_rows):
|
||||
"""Cross-link selected metadata; never pretend untransferred bulk is local."""
|
||||
expected, omitted = {}, {}
|
||||
controls = [dict(path=name, bytes=len(cached['evidence/' + name]), sha256=sha(cached['evidence/' + name])) for name in CONTROLS]
|
||||
for row in [*manifest_rows.values(), *controls]:
|
||||
destination, reason = selected(row)
|
||||
if destination:
|
||||
require(destination not in expected, 'Selection maps multiple files to one destination')
|
||||
expected[destination] = dict(path=destination, bytes=row['bytes'], sha256=row['sha256'])
|
||||
else: omitted[row['path']] = dict(row, reason=reason)
|
||||
require({k: v for k, v in rows.items() if k not in GENERATED} == expected, 'Receipt selection is not the exact sealed policy inventory')
|
||||
actual_omitted = {}
|
||||
require(type(receipt.get('experiment_payload_not_copied')) is list, 'Missing omitted inventory')
|
||||
for row in receipt['experiment_payload_not_copied']:
|
||||
require(type(row) is dict and set(row) == {'path', 'bytes', 'sha256', 'reason'} and row['path'] not in actual_omitted, 'Malformed omitted inventory')
|
||||
file_row({k: row[k] for k in ('path', 'bytes', 'sha256')}); actual_omitted[row['path']] = row
|
||||
require(actual_omitted == omitted, 'Omitted inventory contradicts selection policy')
|
||||
index = json_bytes(cached['evidence-index.json']); indexed = {}
|
||||
require(type(index) is list, 'Invalid evidence index')
|
||||
for item in index:
|
||||
require(type(item) is dict and set(item) == {'source', 'copy', 'bytes', 'sha256'} and item['copy'] in expected and item['copy'] not in indexed,
|
||||
'Invalid/duplicate indexed file')
|
||||
name = item['copy']; original = ('protocol/' + name if name.startswith('source-snapshot/') else name[len('evidence/'):])
|
||||
require(item == dict(source=EXP_REL + '/' + original, copy=name, bytes=expected[name]['bytes'], sha256=expected[name]['sha256']), 'Evidence index source/hash mismatch')
|
||||
indexed[name] = item
|
||||
require(set(indexed) == set(expected) and receipt.get('copied_evidence_files') == len(expected) and
|
||||
receipt.get('copied_evidence_bytes') == sum(x['bytes'] for x in expected.values()), 'Evidence index coverage/count mismatch')
|
||||
proof = json_bytes(cached['seal-verification.json'])
|
||||
require(proof.get('pass_all') is True and proof.get('root') == REMOTE_ROOT + '/' + EXP_REL and
|
||||
proof.get('manifest_sha256') == receipt['experiment_manifest_sha256'] and
|
||||
proof.get('checksums_sha256') == receipt['experiment_checksums_sha256'] and proof.get('payload_file_count') == len(manifest_rows) and
|
||||
proof.get('payload_bytes') == sum(x['bytes'] for x in manifest_rows.values()), 'Seal proof disagrees with controls')
|
||||
reg_raw, final_raw, analysis_raw = (cached['evidence/' + name] for name in
|
||||
('protocol/preregistered.json', 'audit/final-status.json', 'audit/engineering-analysis.json'))
|
||||
require(sha(reg_raw) == PREREG_SHA and sha(final_raw) == receipt['final_sha256'] and sha(analysis_raw) == receipt['analysis_sha256'], 'Prereg/final/analysis anchor mismatch')
|
||||
reg, final, analysis = map(json_bytes, (reg_raw, final_raw, analysis_raw))
|
||||
require(final.get('status') == analysis.get('status') == receipt['declared_status'] and final.get('analysis_sha256') == receipt['analysis_sha256'] and
|
||||
final.get('registration_sha256') == analysis.get('registration_sha256') == PREREG_SHA and
|
||||
final.get('analysis_recomputed_exactly') is True and final.get('completed_calls') == 32 and final.get('executed_ticks') == 11074 and
|
||||
analysis.get('expected_calls') == analysis.get('analyzed_calls') == 32 and analysis.get('executed_ticks') == 11074,
|
||||
'Final/analysis scope or linkage mismatch')
|
||||
require(final.get('training') is False and final.get('learned_candidate') is False and final.get('learning_pass_claimed') is False and
|
||||
analysis.get('training') is False and analysis.get('candidate_generated') is False and analysis.get('learning_pass_claimed') is False,
|
||||
'Final/analysis asserts learning or training')
|
||||
numerical, covered = analysis.get('numerical_exact'), analysis.get('nonvacuous_temporal_coverage')
|
||||
require(type(numerical) is type(covered) is bool and receipt.get('declared_numerical_exact') is numerical and
|
||||
receipt.get('declared_live_coverage') is covered and analysis.get('pass_all') is (numerical and covered) and
|
||||
receipt.get('declared_engineering_pass') is analysis['pass_all'] and final['status'] ==
|
||||
('FAIL-NUMERICAL' if not numerical else (PASS if covered else 'INCOMPLETE-COVERAGE')), 'Engineering status was promoted or contradicted')
|
||||
sources = json_bytes(cached['source-identity.json'])
|
||||
require(type(sources) is list and len(sources) == 21, 'Source identity inventory missing')
|
||||
expected_sources, seen = {}, set()
|
||||
for item in sources:
|
||||
require(type(item) is dict and set(item) == {'model_source', 'sealed_source', 'copy', 'bytes', 'sha256'}, 'Invalid source identity row')
|
||||
name = item['copy']; require(name in {'source-snapshot/' + n for n in SOURCE_FILES} and name not in seen, 'Unexpected/duplicate source identity')
|
||||
seen.add(name); filename = PurePosixPath(name).name
|
||||
expected_item = dict(model_source=MODEL_REL + '/' + filename, sealed_source=EXP_REL + '/protocol/' + name,
|
||||
copy=name, bytes=rows[name]['bytes'], sha256=rows[name]['sha256'])
|
||||
require(item == expected_item, 'Source identity disagrees with registered snapshot')
|
||||
expected_sources[REMOTE_ROOT + '/' + item['model_source']] = item['sha256']
|
||||
require(reg.get('input_sha256', {}).get(REMOTE_ROOT + '/' + item['sealed_source']) == item['sha256'], 'Snapshot missing from preregistered input authority')
|
||||
require(reg.get('source_sha256') == analysis.get('source_sha256') == expected_sources, 'Original 21-source inventory differs from source identities')
|
||||
|
||||
|
||||
class BoundedSFTP:
|
||||
def __init__(self, sftp, byte_budget=MAX_TOTAL_BYTES):
|
||||
self.sftp, self.byte_budget, self.bytes_read = sftp, byte_budget, 0
|
||||
|
||||
def read(self, remote, size=None, checksum=None):
|
||||
require(remote.startswith(REGISTRY_ROOT + '/'), 'Only the fixed AN registry may be downloaded')
|
||||
relative_name(remote[len(REGISTRY_ROOT) + 1:])
|
||||
parent = PurePosixPath(remote).parent
|
||||
for directory in reversed((parent, *parent.parents)):
|
||||
require(stat.S_ISDIR(self.sftp.lstat(str(directory)).st_mode), 'Remote symlink/non-directory')
|
||||
info = self.sftp.lstat(remote)
|
||||
require(stat.S_ISREG(info.st_mode), 'Remote symlink/nonregular file')
|
||||
require(0 <= info.st_size <= MAX_FILE_BYTES and (size is None or info.st_size == size), 'Remote size outside bounded inventory')
|
||||
require(self.bytes_read + info.st_size <= self.byte_budget, 'Total SFTP read budget exceeded')
|
||||
chunks, total = [], 0
|
||||
with self.sftp.open(remote, 'rb') as stream:
|
||||
while total < info.st_size:
|
||||
data = stream.read(min(1 << 20, info.st_size - total, self.byte_budget - self.bytes_read))
|
||||
require(data, 'Remote file ended before its declared size')
|
||||
total += len(data); self.bytes_read += len(data)
|
||||
require(total <= MAX_FILE_BYTES and self.bytes_read <= self.byte_budget, 'Remote file grew beyond download limit'); chunks.append(data)
|
||||
raw = b''.join(chunks)
|
||||
after = self.sftp.lstat(remote)
|
||||
require(stat.S_ISREG(after.st_mode) and after.st_size == info.st_size and len(raw) == info.st_size and
|
||||
(size is None or len(raw) == size), 'Remote type/size changed during read')
|
||||
require(checksum is None or sha(raw) == checksum, 'Remote file SHA mismatch')
|
||||
return raw
|
||||
|
||||
|
||||
def check_destination(path):
|
||||
require(path.is_absolute() and '..' not in path.parts, 'Destination must be canonical and absolute')
|
||||
for node in (path, *path.parents):
|
||||
require(not node.is_symlink() and not getattr(node, 'is_junction', lambda: False)(), 'Local symlink/junction destination')
|
||||
require(not path.exists(), 'Destination exists; no overwrite or partial retry')
|
||||
|
||||
|
||||
def write_bytes(path, raw):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open('xb') as stream: stream.write(raw)
|
||||
require(path.stat().st_size == len(raw) and sha(path.read_bytes()) == sha(raw), 'Local byte/SHA verification failed')
|
||||
|
||||
|
||||
def fetch(sftp, destination, receipt_sha256, *, max_total_bytes=MAX_TOTAL_BYTES):
|
||||
require(type(receipt_sha256) is str and SHA.fullmatch(receipt_sha256), 'External receipt SHA required')
|
||||
require(type(max_total_bytes) is int and 0 < max_total_bytes <= MAX_TOTAL_BYTES, 'Download cap cannot exceed 1 GiB')
|
||||
destination = Path(destination); check_destination(destination)
|
||||
remote = BoundedSFTP(sftp, max_total_bytes)
|
||||
receipt_raw = remote.read(REGISTRY_ROOT + '/publication-receipt.json', checksum=receipt_sha256)
|
||||
receipt, rows = validate_receipt(receipt_raw, receipt_sha256)
|
||||
total = len(receipt_raw) + sum(row['bytes'] for row in rows.values())
|
||||
require(total + len(receipt_raw) <= max_total_bytes, 'Exact inventory plus final anchor reread exceeds total download budget')
|
||||
essentials = {*GENERATED, 'evidence/manifest.json', 'evidence/checksums.sha256',
|
||||
'evidence/protocol/preregistered.json', 'evidence/audit/final-status.json', 'evidence/audit/engineering-analysis.json'}
|
||||
cached = {name: remote.read(REGISTRY_ROOT + '/' + name, rows[name]['bytes'], rows[name]['sha256']) for name in sorted(essentials)}
|
||||
manifest_rows = validate_manifest(cached['evidence/manifest.json'], cached['evidence/checksums.sha256'], receipt)
|
||||
validate_links(cached, receipt, rows, manifest_rows)
|
||||
check_destination(destination); destination.mkdir(parents=True, exist_ok=False)
|
||||
try:
|
||||
write_bytes(destination / 'publication-receipt.json', receipt_raw)
|
||||
downloaded = [dict(path='publication-receipt.json', bytes=len(receipt_raw), sha256=receipt_sha256)]
|
||||
for name, row in sorted(rows.items()):
|
||||
raw = cached[name] if name in cached else remote.read(REGISTRY_ROOT + '/' + name, row['bytes'], row['sha256'])
|
||||
require(len(raw) == row['bytes'] and sha(raw) == row['sha256'], 'Cached/downloaded byte mismatch')
|
||||
write_bytes(destination / name, raw); downloaded.append(dict(row))
|
||||
remote.read(REGISTRY_ROOT + '/publication-receipt.json', len(receipt_raw), receipt_sha256)
|
||||
expected_names = {row['path'] for row in downloaded}
|
||||
require({x.relative_to(destination).as_posix() for x in destination.rglob('*') if x.is_file()} == expected_names and
|
||||
not any(x.is_symlink() or getattr(x, 'is_junction', lambda: False)() for x in destination.rglob('*')), 'Local unexpected/linked payload')
|
||||
require(all((destination / row['path']).stat().st_size == row['bytes'] and sha((destination / row['path']).read_bytes()) == row['sha256']
|
||||
for row in downloaded), 'Local payload changed during fetch')
|
||||
result = dict(fetch_complete=True, publication_receipt_sha256=receipt_sha256, declared_status=receipt['declared_status'],
|
||||
experiment_manifest_sha256=receipt['experiment_manifest_sha256'], final_sha256=receipt['final_sha256'], preregistration_sha256=PREREG_SHA,
|
||||
registry_relative_names_preserved=True, exact_receipt_inventory_downloaded=True, files=downloaded,
|
||||
downloaded_file_count=len(downloaded), downloaded_bytes=total, total_download_limit_bytes=max_total_bytes,
|
||||
sftp_bytes_read_including_anchor_reread=remote.bytes_read,
|
||||
manifest_self_checksum_and_selected_links_verified=True, source_snapshot_count=21,
|
||||
original_model_sources_downloaded=False, remote_bulk_downloaded=False, remote_bulk_rehashed_locally=False,
|
||||
full_remote_seal_verification_is_publisher_claim=True, complete_bulk_backup=False,
|
||||
engineering_gate_executed=False, fetch_success_is_not_engineering_or_learning_pass=True,
|
||||
learning_pass_claimed=False, candidate_generated=False, existing_files_modified=False)
|
||||
raw = (json.dumps(result, sort_keys=True, indent=2, allow_nan=False) + '\n').encode()
|
||||
require(len(raw) <= MAX_FILE_BYTES, 'Fetch receipt exceeds 4 MiB')
|
||||
write_bytes(destination / 'fetch-verification.json', raw)
|
||||
return result
|
||||
except Exception as error:
|
||||
path = destination / 'fetch-failure.json'
|
||||
if not path.exists():
|
||||
write_bytes(path, (json.dumps(dict(fetch_complete=False, error_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)
|
||||
parser.add_argument('--destination', type=Path, default=DEFAULT_DESTINATION)
|
||||
parser.add_argument('--host', default='192.168.10.51'); parser.add_argument('--port', type=int, default=22)
|
||||
parser.add_argument('--user-env', default='MALECNS_REMOTE_USER')
|
||||
parser.add_argument('--password-env', default='MALECNS_REMOTE_PASSWORD')
|
||||
parser.add_argument('--known-hosts', type=Path, default=Path.home() / '.ssh/known_hosts')
|
||||
args = parser.parse_args()
|
||||
require(SHA.fullmatch(args.receipt_sha256), 'External receipt SHA required'); check_destination(args.destination)
|
||||
require(args.known_hosts.is_file(), 'Existing known_hosts required; trust must be configured separately')
|
||||
username, password = os.environ.get(args.user_env), os.environ.get(args.password_env)
|
||||
require(username and password, 'Credential environment variables are required')
|
||||
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=username, password=password, allow_agent=False, look_for_keys=False,
|
||||
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', 'declared_status', 'publication_receipt_sha256', 'downloaded_file_count')}), flush=True)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,279 @@
|
||||
"""Exclusive small-evidence delivery of a completed, sealed AN engineering gate.
|
||||
|
||||
The two external hashes are mandatory. This tool verifies bytes and declared
|
||||
completion, not numerical science, and never imports an AN execution module.
|
||||
Only the 21 registered source snapshots are delivered; the mutable model tree
|
||||
is neither copied nor declared sealed. Failed/incomplete engineering results
|
||||
retain their exact status. Partial publications are preserved, never resumed.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path, PurePosixPath
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
ROOT = Path('/data/digital-drosophila')
|
||||
MODEL_REL = Path('models/male-cns-lif/olfactory-conditioning-lab-v1')
|
||||
EXP_REL = Path('experiments/phase4an-engineering-validation')
|
||||
REGISTRY_REL = Path('registry/phase4an-engineering-validation')
|
||||
HELPER_REL = Path('models/male-cns-lif/pn-recruitment-audit-v1/publication.py')
|
||||
HELPER_SHA = '7a79c499098b74163e7bb16adabcbc54e7069dafa204d0b99c7f4c03d79f8016'
|
||||
PREREG_SHA = '7317441126f191ccc6e6d28438870b3921537ebe282a6acb163716a690057945'
|
||||
MODULES = ('runtime_light', 'runtime_continuation', 'engineering_inputs', 'engineering_plan',
|
||||
'engineering_compare', 'engineering_queue_audit', 'engineering_prepare',
|
||||
'engineering_workflow', 'engineering_scopes', 'engineering_analysis')
|
||||
SOURCE_FILES = tuple(sorted([m + '.py' for m in MODULES] + ['test_' + m + '.py' for m in MODULES] + ['ENGINEERING_GATE_DRAFT.md']))
|
||||
PASS = 'PASS-EXACT-LIGHT-CONTINUATION-ENGINEERING-NOT-LEARNING'
|
||||
STATUSES = frozenset((PASS, 'FAIL-NUMERICAL', 'INCOMPLETE-COVERAGE'))
|
||||
SUFFIXES = frozenset(('.json', '.csv', '.md', '.log', '.sha256'))
|
||||
MAX_BYTES = 4 * 1024**2
|
||||
REQUIRED = ('protocol/preregistered.json', 'audit/final-status.json', 'audit/engineering-analysis.json',
|
||||
'audit/analysis-execution.json', 'audit/execution-complete.json',
|
||||
'regression/old-integrity-before.json', 'regression/old-integrity-after.json',
|
||||
'regression/AM-seals-after.json')
|
||||
|
||||
|
||||
def require(value, message):
|
||||
if not value:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def digest(path):
|
||||
h = hashlib.sha256()
|
||||
with Path(path).open('rb') as stream:
|
||||
for data in iter(lambda: stream.read(8 << 20), b''):
|
||||
h.update(data)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def is_sha(value):
|
||||
return type(value) is str and re.fullmatch('[0-9a-f]{64}', value) is not None
|
||||
|
||||
|
||||
def load_helper(path):
|
||||
raw = Path(path).read_bytes()
|
||||
require(hashlib.sha256(raw).hexdigest() == HELPER_SHA, 'AI publication helper pin mismatch')
|
||||
spec = importlib.util.spec_from_file_location('phase4an_delivery_pinned_AI', path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
exec(compile(raw, str(path), 'exec'), module.__dict__)
|
||||
return module
|
||||
|
||||
|
||||
def _small_json(helper, experiment, rows, name):
|
||||
require(name in rows and rows[name]['bytes'] <= MAX_BYTES, 'Required small evidence missing/oversize: ' + name)
|
||||
path = helper._regular_file(experiment, name)
|
||||
require(digest(path) == rows[name]['sha256'], 'Evidence changed: ' + name)
|
||||
return helper._json_bytes(path.read_bytes())
|
||||
|
||||
|
||||
def _write(path, value):
|
||||
raw = (json.dumps(value, sort_keys=True, indent=2, ensure_ascii=False, allow_nan=False) + '\n').encode('utf-8')
|
||||
require(len(raw) <= MAX_BYTES, 'Generated delivery evidence exceeds 4 MiB')
|
||||
with Path(path).open('xb') as stream:
|
||||
stream.write(raw)
|
||||
|
||||
|
||||
def _sources(helper, root, experiment, rows, reg):
|
||||
model = root / MODEL_REL
|
||||
expected_paths = {str(model / name) for name in SOURCE_FILES}
|
||||
require(type(reg.get('source_sha256')) is dict and set(reg['source_sha256']) == expected_paths,
|
||||
'Registered source inventory is not the exact 21 used files')
|
||||
actual_snapshots = {p for p in rows if p.startswith('protocol/source-snapshot/')}
|
||||
require(actual_snapshots == {'protocol/source-snapshot/' + name for name in SOURCE_FILES}, 'Snapshot inventory differs from the 21 used sources')
|
||||
proof = []
|
||||
for name in SOURCE_FILES:
|
||||
relative = 'protocol/source-snapshot/' + name
|
||||
original = helper._regular_file(root, (MODEL_REL / name).as_posix())
|
||||
snapshot = helper._regular_file(experiment, relative)
|
||||
expected = reg['source_sha256'][str(original)]
|
||||
require(is_sha(expected) and rows[relative]['sha256'] == expected and
|
||||
reg['input_sha256'].get(str(snapshot)) == expected and digest(original) == digest(snapshot) == expected,
|
||||
'Actual used source / registered snapshot mismatch: ' + name)
|
||||
require(original.stat().st_size == rows[relative]['bytes'] <= MAX_BYTES, 'Used source exceeds small delivery limit')
|
||||
proof.append(dict(model_source=(MODEL_REL / name).as_posix(), sealed_source=(EXP_REL / relative).as_posix(),
|
||||
copy='source-snapshot/' + name, bytes=rows[relative]['bytes'], sha256=expected))
|
||||
return proof
|
||||
|
||||
|
||||
def _declared_evidence(helper, experiment, rows):
|
||||
evidence = {name: _small_json(helper, experiment, rows, name) for name in REQUIRED}
|
||||
reg = evidence['protocol/preregistered.json']; final = evidence['audit/final-status.json']
|
||||
analysis = evidence['audit/engineering-analysis.json']; execution = evidence['audit/execution-complete.json']
|
||||
require(reg.get('schema') == 'phase4an-engineering-registration-v1' and reg.get('training') is False, 'Wrong registration scope')
|
||||
require(final.get('schema') == 'phase4an-engineering-final-v1' and final.get('status') in STATUSES and
|
||||
final.get('registration_sha256') == PREREG_SHA and final.get('analysis_recomputed_exactly') is True,
|
||||
'Missing finalized/recomputed engineering evidence')
|
||||
require(analysis.get('schema') == 'phase4an-engineering-analysis-v1' and analysis.get('status') == final['status'] and
|
||||
analysis.get('registration_sha256') == PREREG_SHA and analysis.get('source_sha256') == reg['source_sha256'],
|
||||
'Final/analysis/registration disagreement')
|
||||
for obj in (final, analysis, execution):
|
||||
require(obj.get('training') is False and obj.get('learning_pass_claimed') is False, 'Unexpected learning/training claim')
|
||||
require(final.get('learned_candidate') is False and analysis.get('candidate_generated') is False and
|
||||
analysis.get('GPU_used') is False and analysis.get('no_new_brain_runs_during_analysis') is True,
|
||||
'Unexpected candidate or analysis scope')
|
||||
require(final.get('model_directory_sealed') is False and final.get('used_engineering_source_snapshots_sealed') is True,
|
||||
'Do not declare the mutable model directory sealed')
|
||||
for obj, key, expected in ((final, 'completed_calls', 32), (final, 'executed_ticks', 11074),
|
||||
(analysis, 'expected_calls', 32), (analysis, 'analyzed_calls', 32), (analysis, 'executed_ticks', 11074),
|
||||
(execution, 'expected_calls', 32), (execution, 'executed_ticks', 11074)):
|
||||
require(type(obj.get(key)) is int and obj[key] == expected, 'Incomplete declared engineering inventory: ' + key)
|
||||
require(all(type(analysis.get(k)) is bool for k in ('pass_all', 'numerical_exact', 'nonvacuous_temporal_coverage')),
|
||||
'Missing explicit numerical/coverage declarations')
|
||||
numerical, covered = analysis['numerical_exact'], analysis['nonvacuous_temporal_coverage']
|
||||
declared = 'FAIL-NUMERICAL' if not numerical else (PASS if covered else 'INCOMPLETE-COVERAGE')
|
||||
require(final['status'] == declared and analysis['pass_all'] is (numerical and covered), 'Declared status contradicts its numerical/coverage flags')
|
||||
analysis_sha = rows['audit/engineering-analysis.json']['sha256']
|
||||
analysis_exec = evidence['audit/analysis-execution.json']
|
||||
require(final.get('analysis_sha256') == analysis_sha and analysis_exec.get('analysis_sha256') == analysis_sha and
|
||||
analysis_exec.get('registration_sha256') == PREREG_SHA, 'Final analysis SHA linkage mismatch')
|
||||
plan = reg.get('plan', {}); planned = plan.get('rows')
|
||||
require(plan.get('total_calls') == 32 and plan.get('total_ticks') == 11074 and type(planned) is list and len(planned) == 32,
|
||||
'Incomplete registered plan')
|
||||
ids = [row.get('row_id') for row in planned]
|
||||
require(all(type(x) is str and re.fullmatch('[a-zA-Z0-9_-]+', x) for x in ids) and len(set(ids)) == 32 and
|
||||
all(type(row.get('steps')) is int and row['steps'] > 0 for row in planned) and sum(row['steps'] for row in planned) == 11074,
|
||||
'Invalid registered call identities/steps')
|
||||
complete = execution.get('completed')
|
||||
require(execution.get('complete') is True and execution.get('registration_sha256') == PREREG_SHA and
|
||||
type(complete) is list and len(complete) == 32, 'Incomplete execution receipt inventory')
|
||||
for row, done in zip(planned, complete):
|
||||
identity = row['row_id']; name = 'receipts/' + identity + '.json'
|
||||
receipt = _small_json(helper, experiment, rows, name)
|
||||
runtime_name = 'receipts/' + identity + '.runtime.json'
|
||||
runtime = _small_json(helper, experiment, rows, runtime_name)
|
||||
require(done.get('row_id') == identity and done.get('execution_sha256') == rows[name]['sha256'], 'Execution order/receipt SHA mismatch')
|
||||
require(receipt.get('complete') is True and type(receipt.get('process_exit_code')) is int and receipt['process_exit_code'] == 0 and
|
||||
receipt.get('row_id') == identity and receipt.get('registration_sha256') == PREREG_SHA and
|
||||
receipt.get('runtime_record_sha256') == rows[runtime_name]['sha256'], 'Unsuccessful/unbound process receipt')
|
||||
require(runtime.get('complete') is True and runtime.get('training') is False and runtime.get('registration_sha256') == PREREG_SHA and
|
||||
runtime.get('row') == row, 'Runtime row does not match its registered descriptor')
|
||||
for field, suffix in (('stdout_sha256', '.stdout.log'), ('stderr_sha256', '.stderr.log')):
|
||||
log = 'logs/' + identity + suffix
|
||||
require(log in rows and receipt.get(field) == rows[log]['sha256'], 'Execution log linkage mismatch')
|
||||
checkpoint = 'runs/' + identity + '/checkpoint-manifest.json'
|
||||
require(checkpoint in rows and receipt.get('checkpoint_manifest_sha256') == runtime.get('checkpoint_manifest_sha256') == rows[checkpoint]['sha256'],
|
||||
'Execution checkpoint linkage mismatch')
|
||||
before, after = (evidence['regression/old-integrity-' + point + '.json'] for point in ('before', 'after'))
|
||||
require(type(before) is dict and type(after) is dict and len(before) == len(after) == 28 and set(before) == set(after) and
|
||||
all(type(r) is dict and r.get('pass') is True for r in [*before.values(), *after.values()]) and
|
||||
final.get('old_integrity_pass') is True and final.get('old_integrity_roots') == 28 and final.get('AM_seals_preserved') is True,
|
||||
'Finalized frozen-integrity evidence missing/failed')
|
||||
for name in before:
|
||||
for report in (before[name], after[name]):
|
||||
require(report.get('mismatches', []) == [], 'Frozen integrity report contains mismatches')
|
||||
for key in ('manifest_sha256', 'checksums_sha256', 'reference_sha256'):
|
||||
if key in before[name] or key in after[name]:
|
||||
require(is_sha(before[name].get(key)) and before[name][key] == after[name].get(key),
|
||||
'Frozen integrity authority changed: ' + name)
|
||||
am = evidence['regression/AM-seals-after.json']
|
||||
require(type(am) is dict and set(am) == {'model', 'experiment'} and all(am[k].get('pass_all') is True and
|
||||
am[k].get('manifest_sha256') == reg['anchors']['am_' + k + '_manifest_sha256'] for k in am), 'AM seal evidence contradicts registered anchors')
|
||||
return reg, final, analysis
|
||||
|
||||
|
||||
def selection(row):
|
||||
"""Return a canonical destination or an explicit omitted reason."""
|
||||
path = PurePosixPath(row['path'])
|
||||
if path.parts[0] == 'extension-build':
|
||||
return None, 'build_artifact'
|
||||
if row['bytes'] > MAX_BYTES:
|
||||
return None, 'exceeds_4_MiB'
|
||||
if path.parts[:2] == ('protocol', 'source-snapshot') and len(path.parts) == 3 and path.name in SOURCE_FILES:
|
||||
return 'source-snapshot/' + path.name, None
|
||||
if path.suffix.lower() not in SUFFIXES:
|
||||
return None, 'bulk_binary_or_unselected_suffix_including_jsonl'
|
||||
return 'evidence/' + row['path'], None
|
||||
|
||||
|
||||
def publish(experiment_manifest_sha, final_sha, *, root=ROOT):
|
||||
require(sys.dont_write_bytecode, 'Use python -B; registered sources remain read-only')
|
||||
require(is_sha(experiment_manifest_sha) and is_sha(final_sha), 'Two actual external manifest/final SHA-256 anchors are mandatory')
|
||||
root = Path(root)
|
||||
require(root.is_absolute() and root.is_dir() and not root.is_symlink(), 'Invalid project root')
|
||||
root = root.resolve(strict=True)
|
||||
publisher_sha = digest(__file__)
|
||||
experiment, registry = root / EXP_REL, root / REGISTRY_REL
|
||||
for relative in (EXP_REL, MODEL_REL, REGISTRY_REL, HELPER_REL):
|
||||
current = root
|
||||
for part in relative.parts:
|
||||
current = current / part
|
||||
require(not current.is_symlink(), 'Symlink in publication path')
|
||||
require(not registry.exists(), 'Registry exists; no overwrite or retry')
|
||||
helper_path = root / HELPER_REL; helper = load_helper(helper_path)
|
||||
require(digest(experiment / 'manifest.json') == experiment_manifest_sha and
|
||||
digest(experiment / 'audit/final-status.json') == final_sha and
|
||||
digest(experiment / 'protocol/preregistered.json') == PREREG_SHA, 'External/fixed authority mismatch')
|
||||
verified = helper.verify_seal(experiment)
|
||||
require(verified['manifest_sha256'] == experiment_manifest_sha, 'Experiment authority changed during verification')
|
||||
rows = {row['path']: row for row in verified['files']}
|
||||
require(not any(PurePosixPath(name).name in ('stopped.json', 'preparation-failure.json', 'error.json', 'runtime-error.json') for name in rows),
|
||||
'Execution/error partial cannot be published as a completed engineering gate')
|
||||
reg, final, analysis = _declared_evidence(helper, experiment, rows)
|
||||
source_proof = _sources(helper, root, experiment, rows, reg)
|
||||
plans, omitted = [], []
|
||||
for row in verified['files'] + helper._control_rows(experiment, verified):
|
||||
destination, reason = selection(row)
|
||||
if destination:
|
||||
plans.append((row, destination))
|
||||
else:
|
||||
omitted.append(dict(row, reason=reason))
|
||||
targets = {destination for _, destination in plans}
|
||||
require(len(targets) == len(plans) and all('evidence/' + name in targets for name in (*REQUIRED, 'manifest.json', 'checksums.sha256')),
|
||||
'Required controls/evidence cannot fit the fixed small-copy policy')
|
||||
require(all('source-snapshot/' + name in targets for name in SOURCE_FILES), 'Missing deliverable registered source')
|
||||
registry.parent.mkdir(parents=True, exist_ok=True); registry.mkdir(exist_ok=False)
|
||||
try:
|
||||
records = []
|
||||
for row, destination in plans:
|
||||
source = helper._regular_file(experiment, row['path'])
|
||||
helper._copy_verified(source, registry / destination, row['bytes'], row['sha256'])
|
||||
records.append(dict(source=(EXP_REL / row['path']).as_posix(), copy=destination, bytes=row['bytes'], sha256=row['sha256']))
|
||||
require(helper.verify_seal(experiment) == verified and _sources(helper, root, experiment, rows, reg) == source_proof and
|
||||
digest(helper_path) == HELPER_SHA and digest(__file__) == publisher_sha,
|
||||
'Sealed payload, helper, publisher or original used sources changed during publication')
|
||||
_write(registry / 'seal-verification.json', {k: v for k, v in verified.items() if k != 'files'})
|
||||
_write(registry / 'evidence-index.json', records)
|
||||
_write(registry / 'source-identity.json', source_proof)
|
||||
files = [dict(path=path.relative_to(registry).as_posix(), bytes=path.stat().st_size, sha256=digest(path))
|
||||
for path in sorted(registry.rglob('*')) if path.is_file()]
|
||||
receipt = dict(schema='phase4an-engineering-publication-v1', publication_complete=True,
|
||||
registry=REGISTRY_REL.as_posix(), project_root=str(root), declared_status=final['status'],
|
||||
declared_numerical_exact=analysis['numerical_exact'], declared_live_coverage=analysis['nonvacuous_temporal_coverage'],
|
||||
declared_engineering_pass=analysis['pass_all'], completed_calls=32, executed_ticks=11074,
|
||||
experiment_manifest_sha256=experiment_manifest_sha, experiment_checksums_sha256=verified['checksums_sha256'],
|
||||
final_sha256=final_sha, analysis_sha256=final['analysis_sha256'], preregistration_sha256=PREREG_SHA,
|
||||
publisher_sha256=publisher_sha, helper_sha256=HELPER_SHA, helper_globals_modified=False,
|
||||
used_sources_verified_before_and_after=True, used_source_count=21, model_directory_sealed=False,
|
||||
model_files_copied=False, copied_evidence_files=len(records), copied_evidence_bytes=sum(r['bytes'] for r in records),
|
||||
source_identity='source-identity.json', files=files, experiment_payload_not_copied=omitted,
|
||||
selection_policy=dict(max_file_bytes=MAX_BYTES, suffixes=sorted(SUFFIXES),
|
||||
source_snapshot_names=list(SOURCE_FILES), excluded_tree='extension-build', all_jsonl_excluded=True),
|
||||
complete_checksums_reference_bulk_not_copied=True, bulk_remains_at=EXP_REL.as_posix(), complete_bulk_backup=False,
|
||||
numerical_gate_rerun_by_publisher=False, publication_success_is_not_engineering_or_learning_pass=True,
|
||||
training=False, candidate_generated=False, learning_pass_claimed=False, existing_files_modified=False,
|
||||
receipt_self_hash='Excluded from files; its SHA is returned externally, never embedded in itself')
|
||||
path = registry / 'publication-receipt.json'; _write(path, receipt)
|
||||
return dict(publication_complete=True, declared_status=final['status'], registry=str(registry),
|
||||
copied_evidence_files=len(records), receipt_sha256=digest(path))
|
||||
except Exception as error:
|
||||
failure = registry / 'publication-failure.json'
|
||||
if not failure.exists():
|
||||
_write(failure, dict(publication_complete=False, error_type=type(error).__name__, error=str(error), partial_outputs_preserved=True))
|
||||
raise
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--experiment-manifest-sha256', required=True)
|
||||
parser.add_argument('--final-sha256', required=True)
|
||||
args = parser.parse_args()
|
||||
print(json.dumps(publish(args.experiment_manifest_sha256, args.final_sha256), ensure_ascii=False), flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,229 @@
|
||||
"""In-memory SFTP / temporary local files only; no real server access."""
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import stat
|
||||
import tempfile
|
||||
import types
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import fetch_phase4an as f
|
||||
|
||||
|
||||
def data(value):
|
||||
return (json.dumps(value, sort_keys=True, allow_nan=False) + '\n').encode()
|
||||
|
||||
|
||||
def row(name, raw):
|
||||
return dict(path=name, bytes=len(raw), sha256=f.sha(raw))
|
||||
|
||||
|
||||
class FakeSFTP:
|
||||
def __init__(self, files):
|
||||
self.files, self.opened, self.symlinks, self.hook = files, [], set(), None
|
||||
|
||||
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(x.startswith(name.rstrip('/') + '/') for x 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 writes are forbidden')
|
||||
self.opened.append(name)
|
||||
if self.hook: self.hook(name)
|
||||
return io.BytesIO(self.files[name])
|
||||
|
||||
|
||||
def fixture(status=f.PASS):
|
||||
payload, sources, inputs, identities = {}, {}, {}, []
|
||||
for name in f.SOURCE_FILES:
|
||||
content = ('# synthetic ' + name).encode(); path = 'protocol/source-snapshot/' + name
|
||||
payload[path] = content
|
||||
sources[f.REMOTE_ROOT + '/' + f.MODEL_REL + '/' + name] = f.sha(content)
|
||||
inputs[f.REMOTE_ROOT + '/' + f.EXP_REL + '/' + path] = f.sha(content)
|
||||
identities.append(dict(model_source=f.MODEL_REL + '/' + name, sealed_source=f.EXP_REL + '/' + path,
|
||||
copy='source-snapshot/' + name, bytes=len(content), sha256=f.sha(content)))
|
||||
payload['protocol/preregistered.json'] = data(dict(source_sha256=sources, input_sha256=inputs))
|
||||
prereg = f.sha(payload['protocol/preregistered.json'])
|
||||
numerical, covered = status != 'FAIL-NUMERICAL', status == f.PASS
|
||||
analysis = dict(status=status, registration_sha256=prereg, source_sha256=sources, expected_calls=32,
|
||||
analyzed_calls=32, executed_ticks=11074, numerical_exact=numerical, nonvacuous_temporal_coverage=covered,
|
||||
pass_all=numerical and covered, training=False, candidate_generated=False, learning_pass_claimed=False)
|
||||
payload['audit/engineering-analysis.json'] = data(analysis)
|
||||
analysis_sha = f.sha(payload['audit/engineering-analysis.json'])
|
||||
final = dict(status=status, analysis_sha256=analysis_sha, registration_sha256=prereg, analysis_recomputed_exactly=True,
|
||||
completed_calls=32, executed_ticks=11074, training=False, learned_candidate=False, learning_pass_claimed=False)
|
||||
payload['audit/final-status.json'] = data(final)
|
||||
payload.update({'runs/example/state.npz': b'bulk', 'runs/example/queue.jsonl': b'bulk queue',
|
||||
'extension-build/build.log': b'not selected', 'logs/readable.log': b'small log'})
|
||||
manifest_rows = [row(name, raw) for name, raw in sorted(payload.items())]
|
||||
manifest = data(dict(root=f.REMOTE_ROOT + '/' + f.EXP_REL, files=manifest_rows))
|
||||
checksums = (''.join(r['sha256'] + ' ' + r['path'] + '\n' for r in manifest_rows) + f.sha(manifest) + ' manifest.json\n').encode()
|
||||
controls = {'manifest.json': manifest, 'checksums.sha256': checksums}
|
||||
registry, evidence_index, omitted = {}, [], []
|
||||
for name, raw in sorted({**payload, **controls}.items()):
|
||||
r = row(name, raw); target, reason = f.selected(r)
|
||||
if target:
|
||||
registry[target] = raw
|
||||
evidence_index.append(dict(source=f.EXP_REL + '/' + name, copy=target, bytes=len(raw), sha256=f.sha(raw)))
|
||||
else: omitted.append(dict(r, reason=reason))
|
||||
proof = dict(pass_all=True, root=f.REMOTE_ROOT + '/' + f.EXP_REL, manifest_sha256=f.sha(manifest), checksums_sha256=f.sha(checksums),
|
||||
payload_file_count=len(manifest_rows), payload_bytes=sum(r['bytes'] for r in manifest_rows))
|
||||
registry['seal-verification.json'] = data(proof)
|
||||
registry['evidence-index.json'] = data(evidence_index)
|
||||
registry['source-identity.json'] = data(identities)
|
||||
receipt = dict(schema='phase4an-engineering-publication-v1', publication_complete=True, project_root=f.REMOTE_ROOT,
|
||||
registry=f.REGISTRY_REL, declared_status=status, used_sources_verified_before_and_after=True,
|
||||
complete_checksums_reference_bulk_not_copied=True, publication_success_is_not_engineering_or_learning_pass=True,
|
||||
model_directory_sealed=False, model_files_copied=False, complete_bulk_backup=False, numerical_gate_rerun_by_publisher=False,
|
||||
training=False, candidate_generated=False, learning_pass_claimed=False, existing_files_modified=False, helper_globals_modified=False,
|
||||
selection_policy=dict(max_file_bytes=f.MAX_FILE_BYTES, suffixes=sorted(f.SUFFIXES), source_snapshot_names=list(f.SOURCE_FILES),
|
||||
excluded_tree='extension-build', all_jsonl_excluded=True),
|
||||
used_source_count=21, completed_calls=32, executed_ticks=11074, preregistration_sha256=prereg,
|
||||
source_identity='source-identity.json', bulk_remains_at=f.EXP_REL, experiment_manifest_sha256=f.sha(manifest),
|
||||
experiment_checksums_sha256=f.sha(checksums), final_sha256=f.sha(payload['audit/final-status.json']), analysis_sha256=analysis_sha,
|
||||
publisher_sha256='a'*64, helper_sha256='b'*64, files=[row(name, raw) for name, raw in sorted(registry.items())],
|
||||
experiment_payload_not_copied=omitted, copied_evidence_files=len(evidence_index),
|
||||
copied_evidence_bytes=sum(r['bytes'] for r in evidence_index), declared_numerical_exact=numerical,
|
||||
declared_live_coverage=covered, declared_engineering_pass=numerical and covered)
|
||||
registry['publication-receipt.json'] = data(receipt)
|
||||
return FakeSFTP({f.REGISTRY_ROOT + '/' + name: raw for name, raw in registry.items()}), prereg, f.sha(registry['publication-receipt.json'])
|
||||
|
||||
|
||||
class FetchTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory(prefix='phase4an-fetch-test-'); self.addCleanup(self.temp.cleanup)
|
||||
self.destination = Path(self.temp.name).resolve() / 'report'
|
||||
self.sftp, self.prereg, self.pin = fixture()
|
||||
|
||||
def fetch(self, **kwargs):
|
||||
with patch.object(f, 'PREREG_SHA', self.prereg):
|
||||
return f.fetch(self.sftp, self.destination, self.pin, **kwargs)
|
||||
|
||||
def receipt(self): return f.json_bytes(self.sftp.files[f.REGISTRY_ROOT + '/publication-receipt.json'])
|
||||
|
||||
def resign(self, receipt):
|
||||
raw = data(receipt); self.sftp.files[f.REGISTRY_ROOT + '/publication-receipt.json'] = raw; self.pin = f.sha(raw)
|
||||
|
||||
def change_selected(self, name, content):
|
||||
self.sftp.files[f.REGISTRY_ROOT + '/' + name] = content
|
||||
receipt = self.receipt(); next(r for r in receipt['files'] if r['path'] == name).update(bytes=len(content), sha256=f.sha(content))
|
||||
self.resign(receipt)
|
||||
|
||||
def test_exact_small_registry_mirror_and_receipt_hashes_no_model_or_bulk(self):
|
||||
result = self.fetch()
|
||||
self.assertTrue(result['fetch_complete']); self.assertTrue(result['exact_receipt_inventory_downloaded'])
|
||||
self.assertFalse(result['engineering_gate_executed']); self.assertFalse(result['learning_pass_claimed'])
|
||||
self.assertTrue((self.destination / 'evidence/audit/final-status.json').is_file())
|
||||
self.assertTrue((self.destination / 'source-snapshot/runtime_light.py').is_file())
|
||||
self.assertEqual(len(result['files']), len(self.receipt()['files']) + 1)
|
||||
self.assertEqual(result['sftp_bytes_read_including_anchor_reread'], result['downloaded_bytes'] + len(self.sftp.files[f.REGISTRY_ROOT + '/publication-receipt.json']))
|
||||
for r in result['files']:
|
||||
self.assertEqual(f.sha((self.destination / r['path']).read_bytes()), r['sha256'])
|
||||
self.assertTrue(all(name.startswith(f.REGISTRY_ROOT + '/') for name in self.sftp.opened))
|
||||
self.assertFalse(any(name.endswith(('.npz', '.npy', '.jsonl')) or '/extension-build/' in name for name in self.sftp.opened))
|
||||
|
||||
def test_failure_and_incomplete_declarations_never_promoted(self):
|
||||
for status in ('FAIL-NUMERICAL', 'INCOMPLETE-COVERAGE'):
|
||||
with self.subTest(status=status), tempfile.TemporaryDirectory() as directory:
|
||||
sftp, prereg, pin = fixture(status)
|
||||
with patch.object(f, 'PREREG_SHA', prereg): result = f.fetch(sftp, Path(directory).resolve() / 'report', pin)
|
||||
self.assertEqual(result['declared_status'], status)
|
||||
self.assertTrue(result['fetch_success_is_not_engineering_or_learning_pass'])
|
||||
|
||||
def test_external_receipt_pin_and_fixed_prereg_required_before_payload(self):
|
||||
for pin in ('', 'x'*64, '0'*64):
|
||||
self.sftp.opened.clear()
|
||||
with self.assertRaises(ValueError): f.fetch(self.sftp, self.destination, pin)
|
||||
self.assertFalse(self.destination.exists())
|
||||
self.assertTrue(all(name.endswith('/publication-receipt.json') for name in self.sftp.opened))
|
||||
with self.assertRaisesRegex(ValueError, 'authority/scope'): f.fetch(self.sftp, self.destination, self.pin)
|
||||
|
||||
def test_forbidden_paths_and_binary_or_extra_source_fail_before_more_reads(self):
|
||||
for name in ('../bad.json', 'evidence/x/../bad.json', 'evidence/C:/bad.json', 'evidence/CON.log',
|
||||
'source-snapshot/unregistered.py', 'evidence/state.npz', 'evidence/queue.jsonl', 'evidence/extension-build/info.json'):
|
||||
with self.subTest(name=name):
|
||||
self.sftp, self.prereg, self.pin = fixture(); receipt = self.receipt()
|
||||
receipt['files'].append(dict(path=name, bytes=1, sha256='a'*64)); self.resign(receipt)
|
||||
with self.assertRaises(ValueError): self.fetch()
|
||||
self.assertFalse(self.destination.exists()); self.assertEqual(len(self.sftp.opened), 1)
|
||||
|
||||
def test_case_prefix_collision_and_receipt_self_inventory_rejected(self):
|
||||
for name in ('EVIDENCE/audit/final-status.json', 'evidence/audit', 'publication-receipt.json', 'fetch-verification.json'):
|
||||
with self.subTest(name=name):
|
||||
self.sftp, self.prereg, self.pin = fixture(); receipt = self.receipt()
|
||||
receipt['files'].append(dict(path=name, bytes=1, sha256='a'*64)); self.resign(receipt)
|
||||
with self.assertRaises(ValueError): self.fetch()
|
||||
self.assertFalse(self.destination.exists())
|
||||
|
||||
def test_budget_is_hard_cap_and_cannot_be_raised(self):
|
||||
with self.assertRaises(ValueError): self.fetch(max_total_bytes=f.MAX_TOTAL_BYTES + 1)
|
||||
self.assertFalse(self.sftp.opened)
|
||||
with self.assertRaisesRegex(ValueError, 'budget'): self.fetch(max_total_bytes=1)
|
||||
self.assertFalse(self.destination.exists())
|
||||
|
||||
def test_manifest_checksum_link_checked_even_after_receipt_resigned(self):
|
||||
name = 'evidence/checksums.sha256'; raw = self.sftp.files[f.REGISTRY_ROOT + '/' + name]
|
||||
lines = raw.decode().splitlines(); lines[-1] = '0'*64 + ' manifest.json'; changed = ('\n'.join(lines)+'\n').encode()
|
||||
self.change_selected(name, changed); receipt = self.receipt(); receipt['experiment_checksums_sha256'] = f.sha(changed); self.resign(receipt)
|
||||
with self.assertRaisesRegex(ValueError, 'self-SHA'): self.fetch()
|
||||
self.assertFalse(self.destination.exists())
|
||||
|
||||
def test_source_identity_and_index_crosslinks_not_just_receipt_flags(self):
|
||||
for name, mutation in (('source-identity.json', 'model_source'), ('evidence-index.json', 'source')):
|
||||
with self.subTest(name=name):
|
||||
self.sftp, self.prereg, self.pin = fixture(); values = f.json_bytes(self.sftp.files[f.REGISTRY_ROOT + '/' + name])
|
||||
values[0][mutation] = 'wrong/source.py'; self.change_selected(name, data(values))
|
||||
with self.assertRaises(ValueError): self.fetch()
|
||||
self.assertFalse(self.destination.exists())
|
||||
|
||||
def test_exact_omitted_and_selection_inventory_cannot_drop_evidence(self):
|
||||
receipt = self.receipt(); receipt['experiment_payload_not_copied'].pop(); self.resign(receipt)
|
||||
with self.assertRaisesRegex(ValueError, 'Omitted'): self.fetch()
|
||||
self.assertFalse(self.destination.exists())
|
||||
self.sftp, self.prereg, self.pin = fixture(); receipt = self.receipt()
|
||||
receipt['files'] = [r for r in receipt['files'] if r['path'] != 'evidence/logs/readable.log']; self.resign(receipt)
|
||||
with self.assertRaisesRegex(ValueError, 'exact sealed policy'): self.fetch()
|
||||
|
||||
def test_existing_or_alias_destination_denied_without_remote_reads(self):
|
||||
self.destination.mkdir()
|
||||
with self.assertRaisesRegex(ValueError, 'exists'): self.fetch()
|
||||
self.assertFalse(self.sftp.opened)
|
||||
with self.assertRaises(ValueError): f.check_destination(self.destination / '..' / 'other')
|
||||
|
||||
def test_remote_symlink_directory_and_file_denied(self):
|
||||
for remote in (f.REGISTRY_ROOT, f.REGISTRY_ROOT + '/publication-receipt.json'):
|
||||
self.sftp.symlinks.add(remote)
|
||||
with self.assertRaisesRegex(ValueError, 'symlink'): self.fetch()
|
||||
self.assertFalse(self.sftp.opened); self.sftp.symlinks.clear()
|
||||
|
||||
def test_payload_corruption_preserves_partial_and_prevents_retry(self):
|
||||
remote = f.REGISTRY_ROOT + '/evidence/logs/readable.log'; self.sftp.files[remote] = b'x' * len(self.sftp.files[remote])
|
||||
with self.assertRaisesRegex(ValueError, 'SHA'): self.fetch()
|
||||
self.assertTrue((self.destination / 'fetch-failure.json').is_file())
|
||||
self.assertFalse((self.destination / 'fetch-verification.json').exists())
|
||||
with self.assertRaisesRegex(ValueError, 'exists'): self.fetch()
|
||||
|
||||
def test_remote_receipt_changed_during_download_fails_final_anchor(self):
|
||||
def change(name):
|
||||
if name.endswith('/source-snapshot/runtime_light.py'):
|
||||
self.sftp.files[f.REGISTRY_ROOT + '/publication-receipt.json'] += b' '
|
||||
self.sftp.hook = change
|
||||
with self.assertRaises(ValueError): self.fetch()
|
||||
self.assertTrue((self.destination / 'fetch-failure.json').exists())
|
||||
self.assertFalse((self.destination / 'fetch-verification.json').exists())
|
||||
|
||||
def test_cli_host_trust_credentials_and_default_destination_are_explicit(self):
|
||||
source = Path(f.__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('allow_agent=False, look_for_keys=False', source)
|
||||
self.assertNotIn('AutoAddPolicy', source)
|
||||
self.assertNotIn("add_argument('--password'", source)
|
||||
self.assertEqual(f.DEFAULT_DESTINATION, Path(f.__file__).resolve().parents[2] / 'reports/phase4an-engineering-validation')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,228 @@
|
||||
"""Temporary synthetic payloads only; no real publishing, GPU or old writes."""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import publish_phase4an as p
|
||||
|
||||
|
||||
AI = Path(__file__).resolve().parent.parent / 'pn-recruitment-audit-v1/publication.py'
|
||||
if not AI.is_file():
|
||||
AI = p.ROOT / p.HELPER_REL
|
||||
|
||||
|
||||
def write(path, value):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(value, sort_keys=True), encoding='utf-8')
|
||||
|
||||
|
||||
def read(path):
|
||||
return json.loads(path.read_text(encoding='utf-8'))
|
||||
|
||||
|
||||
def seal(exp):
|
||||
rows = [dict(path=path.relative_to(exp).as_posix(), bytes=path.stat().st_size, sha256=p.digest(path))
|
||||
for path in sorted(exp.rglob('*')) if path.is_file() and path.relative_to(exp).as_posix() not in ('manifest.json', 'checksums.sha256')]
|
||||
write(exp / 'manifest.json', {'root': str(exp), 'files': rows})
|
||||
(exp / 'checksums.sha256').write_text(''.join(r['sha256'] + ' ' + r['path'] + '\n' for r in rows) +
|
||||
p.digest(exp / 'manifest.json') + ' manifest.json\n', encoding='utf-8')
|
||||
return p.digest(exp / 'manifest.json'), p.digest(exp / 'audit/final-status.json')
|
||||
|
||||
|
||||
def fixture(root, status=p.PASS):
|
||||
exp, model = root / p.EXP_REL, root / p.MODEL_REL
|
||||
exp.mkdir(parents=True); model.mkdir(parents=True)
|
||||
helper = root / p.HELPER_REL; helper.parent.mkdir(parents=True); helper.write_bytes(AI.read_bytes())
|
||||
sources, inputs = {}, {}
|
||||
for name in p.SOURCE_FILES:
|
||||
raw = ('# synthetic used source: ' + name + '\n').encode()
|
||||
original, snapshot = model / name, exp / 'protocol/source-snapshot' / name
|
||||
original.write_bytes(raw); snapshot.parent.mkdir(parents=True, exist_ok=True); snapshot.write_bytes(raw)
|
||||
sources[str(original)] = inputs[str(snapshot)] = hashlib.sha256(raw).hexdigest()
|
||||
(model / 'unregistered-science.py').write_bytes(b'not copied or sealed')
|
||||
planned = [dict(row_id='synthetic-' + str(i), steps=348 if i == 31 else 346) for i in range(32)]
|
||||
anchors = dict(am_model_manifest_sha256='a'*64, am_experiment_manifest_sha256='b'*64)
|
||||
reg = dict(schema='phase4an-engineering-registration-v1', training=False, source_sha256=sources, input_sha256=inputs,
|
||||
anchors=anchors, plan=dict(total_calls=32, total_ticks=11074, rows=planned))
|
||||
write(exp / 'protocol/preregistered.json', reg); prereg = p.digest(exp / 'protocol/preregistered.json')
|
||||
completed = []
|
||||
for row in planned:
|
||||
identity = row['row_id']
|
||||
checkpoint = exp / 'runs' / identity / 'checkpoint-manifest.json'; write(checkpoint, {'synthetic': True})
|
||||
runtime = dict(complete=True, training=False, registration_sha256=prereg, row=row, checkpoint_manifest_sha256=p.digest(checkpoint))
|
||||
runtime_path = exp / 'receipts' / (identity + '.runtime.json'); write(runtime_path, runtime)
|
||||
for suffix in ('.stdout.log', '.stderr.log'):
|
||||
target = exp / 'logs' / (identity + suffix); target.parent.mkdir(exist_ok=True); target.write_bytes(b'synthetic log')
|
||||
receipt = dict(complete=True, process_exit_code=0, row_id=identity, registration_sha256=prereg,
|
||||
runtime_record_sha256=p.digest(runtime_path), checkpoint_manifest_sha256=p.digest(checkpoint),
|
||||
stdout_sha256=p.digest(exp / 'logs' / (identity + '.stdout.log')),
|
||||
stderr_sha256=p.digest(exp / 'logs' / (identity + '.stderr.log')))
|
||||
receipt_path = exp / 'receipts' / (identity + '.json'); write(receipt_path, receipt)
|
||||
completed.append(dict(row_id=identity, execution_sha256=p.digest(receipt_path)))
|
||||
write(exp / 'audit/execution-complete.json', dict(complete=True, registration_sha256=prereg, completed=completed,
|
||||
expected_calls=32, executed_ticks=11074, training=False, learning_pass_claimed=False))
|
||||
numerical, covered = status != 'FAIL-NUMERICAL', status == p.PASS
|
||||
analysis = dict(schema='phase4an-engineering-analysis-v1', status=status, registration_sha256=prereg,
|
||||
source_sha256=sources, training=False, learning_pass_claimed=False, candidate_generated=False, GPU_used=False,
|
||||
no_new_brain_runs_during_analysis=True, expected_calls=32, analyzed_calls=32, executed_ticks=11074,
|
||||
pass_all=numerical and covered, numerical_exact=numerical, nonvacuous_temporal_coverage=covered)
|
||||
write(exp / 'audit/engineering-analysis.json', analysis)
|
||||
analysis_sha = p.digest(exp / 'audit/engineering-analysis.json')
|
||||
write(exp / 'audit/analysis-execution.json', dict(registration_sha256=prereg, analysis_sha256=analysis_sha))
|
||||
final = dict(schema='phase4an-engineering-final-v1', status=status, registration_sha256=prereg,
|
||||
analysis_sha256=analysis_sha, analysis_recomputed_exactly=True, completed_calls=32, executed_ticks=11074,
|
||||
training=False, learned_candidate=False, learning_pass_claimed=False, model_directory_sealed=False,
|
||||
used_engineering_source_snapshots_sealed=True, old_integrity_pass=True, old_integrity_roots=28, AM_seals_preserved=True)
|
||||
write(exp / 'audit/final-status.json', final)
|
||||
old = {str(i): {'pass': True} for i in range(28)}
|
||||
write(exp / 'regression/old-integrity-before.json', old); write(exp / 'regression/old-integrity-after.json', old)
|
||||
write(exp / 'regression/AM-seals-after.json', {key: {'pass_all': True, 'manifest_sha256': anchors['am_' + key + '_manifest_sha256']}
|
||||
for key in ('model', 'experiment')})
|
||||
(exp / 'runs/synthetic-0/queue.jsonl').write_bytes(b'bulk queue\n')
|
||||
(exp / 'runs/synthetic-0/state.npz').write_bytes(b'synthetic binary')
|
||||
(exp / 'extension-build').mkdir(); (exp / 'extension-build/build.log').write_bytes(b'not selected even log')
|
||||
return prereg, seal(exp)
|
||||
|
||||
|
||||
class DeliveryTests(unittest.TestCase):
|
||||
def test_success_uses_real_verifier_copies_only_registered_sources_and_small_evidence(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory).resolve(); prereg, anchors = fixture(root)
|
||||
with patch.object(p, 'PREREG_SHA', prereg):
|
||||
result = p.publish(*anchors, root=root)
|
||||
registry = root / p.REGISTRY_REL; receipt = read(registry / 'publication-receipt.json')
|
||||
self.assertTrue(result['publication_complete']); self.assertEqual(result['declared_status'], p.PASS)
|
||||
self.assertEqual(result['receipt_sha256'], p.digest(registry / 'publication-receipt.json'))
|
||||
self.assertEqual(len(list((registry / 'source-snapshot').iterdir())), 21)
|
||||
self.assertFalse((registry / 'model').exists())
|
||||
self.assertFalse(any(x.name == 'unregistered-science.py' for x in registry.rglob('*')))
|
||||
self.assertFalse((registry / 'evidence/runs/synthetic-0/queue.jsonl').exists())
|
||||
self.assertFalse((registry / 'evidence/extension-build/build.log').exists())
|
||||
self.assertEqual(len(receipt['experiment_payload_not_copied']), 3)
|
||||
self.assertNotIn('publication-receipt.json', {x['path'] for x in receipt['files']})
|
||||
self.assertTrue(receipt['complete_checksums_reference_bulk_not_copied'])
|
||||
self.assertFalse(receipt['numerical_gate_rerun_by_publisher'])
|
||||
for row in receipt['files']:
|
||||
self.assertEqual(p.digest(registry / row['path']), row['sha256'])
|
||||
self.assertEqual(p.digest(root / p.EXP_REL / 'manifest.json'), anchors[0])
|
||||
|
||||
def test_failed_and_incomplete_status_are_delivered_without_promotion(self):
|
||||
for status in ('FAIL-NUMERICAL', 'INCOMPLETE-COVERAGE'):
|
||||
with self.subTest(status=status), tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory).resolve(); prereg, anchors = fixture(root, status)
|
||||
with patch.object(p, 'PREREG_SHA', prereg):
|
||||
result = p.publish(*anchors, root=root)
|
||||
receipt = read(root / p.REGISTRY_REL / 'publication-receipt.json')
|
||||
self.assertEqual(result['declared_status'], status)
|
||||
self.assertFalse(receipt['declared_engineering_pass'])
|
||||
self.assertFalse(receipt['learning_pass_claimed'])
|
||||
|
||||
def test_two_external_hashes_are_mandatory_and_fixed_prereg_is_not_inferred(self):
|
||||
with self.assertRaises(TypeError): p.publish()
|
||||
for anchors in ((None, 'a'*64), ('x'*64, 'b'*64)):
|
||||
with self.assertRaises(ValueError): p.publish(*anchors)
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory).resolve(); _, anchors = fixture(root)
|
||||
with self.assertRaisesRegex(ValueError, 'authority mismatch'): p.publish(*anchors, root=root)
|
||||
self.assertFalse((root / p.REGISTRY_REL).exists())
|
||||
|
||||
def test_wrong_external_manifest_or_final_is_rejected_before_writes(self):
|
||||
for position in (0, 1):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory).resolve(); prereg, anchors = fixture(root); bad = list(anchors); bad[position] = 'c'*64
|
||||
with patch.object(p, 'PREREG_SHA', prereg), self.assertRaises(ValueError): p.publish(*bad, root=root)
|
||||
self.assertFalse((root / p.REGISTRY_REL).exists())
|
||||
|
||||
def test_source_mutation_and_missing_source_are_rejected(self):
|
||||
for mutation in ('changed', 'missing'):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory).resolve(); prereg, anchors = fixture(root)
|
||||
source = root / p.MODEL_REL / 'runtime_light.py'
|
||||
if mutation == 'changed': source.write_bytes(b'changed')
|
||||
else: source.unlink()
|
||||
with patch.object(p, 'PREREG_SHA', prereg), self.assertRaises(ValueError): p.publish(*anchors, root=root)
|
||||
self.assertFalse((root / p.REGISTRY_REL).exists())
|
||||
|
||||
def test_snapshot_mutation_rejected_even_if_new_experiment_seal_is_valid(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory).resolve(); prereg, _ = fixture(root); exp = root / p.EXP_REL
|
||||
(exp / 'protocol/source-snapshot/runtime_light.py').write_bytes(b'different')
|
||||
anchors = seal(exp)
|
||||
with patch.object(p, 'PREREG_SHA', prereg), self.assertRaisesRegex(ValueError, 'snapshot mismatch'):
|
||||
p.publish(*anchors, root=root)
|
||||
|
||||
def test_partial_or_unlisted_seal_payload_is_not_ignored(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory).resolve(); prereg, anchors = fixture(root)
|
||||
(root / p.EXP_REL / 'unlisted').write_bytes(b'unlisted')
|
||||
with patch.object(p, 'PREREG_SHA', prereg), self.assertRaises(ValueError): p.publish(*anchors, root=root)
|
||||
|
||||
def test_helper_pin_and_integrity_authority_are_checked(self):
|
||||
for mutation in ('helper', 'old-authority'):
|
||||
with self.subTest(mutation=mutation), tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory).resolve(); prereg, anchors = fixture(root); exp = root / p.EXP_REL
|
||||
if mutation == 'helper':
|
||||
(root / p.HELPER_REL).write_bytes(b'# not the pinned verifier')
|
||||
else:
|
||||
for point, value in (('before', 'a'), ('after', 'b')):
|
||||
path = exp / ('regression/old-integrity-' + point + '.json')
|
||||
record = read(path); record['0']['manifest_sha256'] = value * 64; write(path, record)
|
||||
anchors = seal(exp)
|
||||
with patch.object(p, 'PREREG_SHA', prereg), self.assertRaises(ValueError): p.publish(*anchors, root=root)
|
||||
self.assertFalse((root / p.REGISTRY_REL).exists())
|
||||
|
||||
def test_final_false_success_learning_and_bad_count_rejected(self):
|
||||
for key, value in (('status', 'FAIL-NUMERICAL'), ('learned_candidate', True), ('completed_calls', 31), ('completed_calls', True),
|
||||
('analysis_recomputed_exactly', False), ('model_directory_sealed', True)):
|
||||
with self.subTest(key=key, value=value), tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory).resolve(); prereg, _ = fixture(root); exp = root / p.EXP_REL
|
||||
final = read(exp / 'audit/final-status.json'); final[key] = value; write(exp / 'audit/final-status.json', final)
|
||||
with patch.object(p, 'PREREG_SHA', prereg), self.assertRaises(ValueError): p.publish(*seal(exp), root=root)
|
||||
|
||||
def test_changed_execution_order_or_checkpoint_link_rejected(self):
|
||||
for mutation in ('order', 'checkpoint'):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory).resolve(); prereg, _ = fixture(root); exp = root / p.EXP_REL
|
||||
if mutation == 'order':
|
||||
name = exp / 'audit/execution-complete.json'; data = read(name); data['completed'].reverse(); write(name, data)
|
||||
else:
|
||||
name = exp / 'runs/synthetic-0/checkpoint-manifest.json'; write(name, {'synthetic': False})
|
||||
with patch.object(p, 'PREREG_SHA', prereg), self.assertRaises(ValueError): p.publish(*seal(exp), root=root)
|
||||
|
||||
def test_policy_limits_also_exclude_small_build_json_and_all_jsonl(self):
|
||||
for name in ('extension-build/info.json', 'x/queue.jsonl', 'x/state.npz', 'x/trace.npy', 'x/random.py'):
|
||||
self.assertIsNone(p.selection(dict(path=name, bytes=1))[0])
|
||||
self.assertIsNone(p.selection(dict(path='large.json', bytes=p.MAX_BYTES + 1))[0])
|
||||
self.assertEqual(p.selection(dict(path='protocol/source-snapshot/runtime_light.py', bytes=1))[0], 'source-snapshot/runtime_light.py')
|
||||
|
||||
def test_existing_registry_is_never_overwritten_or_retried(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory).resolve(); prereg, anchors = fixture(root)
|
||||
destination = root / p.REGISTRY_REL; destination.mkdir(parents=True); (destination / 'partial').write_bytes(b'preserve')
|
||||
with patch.object(p, 'PREREG_SHA', prereg), self.assertRaisesRegex(ValueError, 'no overwrite'):
|
||||
p.publish(*anchors, root=root)
|
||||
self.assertEqual((destination / 'partial').read_bytes(), b'preserve')
|
||||
|
||||
def test_copy_failure_preserves_partial_and_disallows_retry(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory).resolve(); prereg, anchors = fixture(root)
|
||||
helper = p.load_helper(root / p.HELPER_REL); original = helper._copy_verified; calls = []
|
||||
def fail_second(*args):
|
||||
calls.append(args)
|
||||
if len(calls) == 2: raise OSError('synthetic write failure')
|
||||
return original(*args)
|
||||
with patch.object(p, 'PREREG_SHA', prereg), patch.object(p, 'load_helper', return_value=helper), patch.object(helper, '_copy_verified', side_effect=fail_second):
|
||||
with self.assertRaises(OSError): p.publish(*anchors, root=root)
|
||||
registry = root / p.REGISTRY_REL
|
||||
self.assertTrue((registry / 'publication-failure.json').is_file())
|
||||
self.assertTrue(calls[0][1].is_file())
|
||||
self.assertFalse((registry / 'publication-receipt.json').exists())
|
||||
with patch.object(p, 'PREREG_SHA', prereg), self.assertRaisesRegex(ValueError, 'no overwrite'):
|
||||
p.publish(*anchors, root=root)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,208 @@
|
||||
"""Temporary Git repositories only; production verification performs no writes."""
|
||||
import contextlib
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import verify_git_payload as v
|
||||
|
||||
|
||||
def data(value):
|
||||
return (json.dumps(value, sort_keys=True, indent=2) + '\n').encode()
|
||||
|
||||
|
||||
def row(name, raw):
|
||||
return dict(path=name, bytes=len(raw), sha256=v.sha(raw))
|
||||
|
||||
|
||||
class TemporaryGit(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.repo = Path(self.temp.name).resolve()
|
||||
self.git('init', '-q')
|
||||
self.git('config', 'user.name', 'Synthetic Test')
|
||||
self.git('config', 'user.email', 'synthetic@example.invalid')
|
||||
self.git('config', 'core.autocrlf', 'false')
|
||||
self.pin = self.fixture()
|
||||
self.stage()
|
||||
|
||||
def tearDown(self):
|
||||
self.temp.cleanup()
|
||||
|
||||
def git(self, *args, input=None):
|
||||
result = subprocess.run(['git', '-C', str(self.repo), *args], input=input,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
self.assertEqual(result.returncode, 0, result.stderr.decode('utf-8', 'replace'))
|
||||
return result.stdout
|
||||
|
||||
def write(self, path, raw):
|
||||
target = self.repo / path; target.parent.mkdir(parents=True, exist_ok=True); target.write_bytes(raw)
|
||||
|
||||
def stage(self):
|
||||
self.git('add', '--', v.MODEL, v.REGISTRY)
|
||||
|
||||
def fixture(self, status='PASS-EXACT-LIGHT-CONTINUATION-ENGINEERING-NOT-LEARNING'):
|
||||
payload, source_rows = {}, []
|
||||
source_sha, input_sha = {}, {}
|
||||
for name in v.SOURCES:
|
||||
raw = ('# synthetic source ' + name + '\n').encode()
|
||||
sealed = 'protocol/source-snapshot/' + name
|
||||
payload[sealed] = raw
|
||||
self.write(v.MODEL + '/' + name, raw)
|
||||
source_sha[v.PROJECT + '/' + v.MODEL + '/' + name] = v.sha(raw)
|
||||
input_sha[v.PROJECT + '/' + v.EXPERIMENT + '/' + sealed] = v.sha(raw)
|
||||
source_rows.append(dict(model_source=v.MODEL + '/' + name, sealed_source=v.EXPERIMENT + '/' + sealed,
|
||||
copy='source-snapshot/' + name, bytes=len(raw), sha256=v.sha(raw)))
|
||||
payload['protocol/preregistered.json'] = data(dict(schema='phase4an-engineering-registration-v1', training=False,
|
||||
source_sha256=source_sha, input_sha256=input_sha))
|
||||
payload['audit/final-status.json'] = data(dict(status=status))
|
||||
payload['audit/engineering-analysis.json'] = data(dict(status=status))
|
||||
payload['audit/small.json'] = data({'synthetic': True})
|
||||
payload['runs/a/state.npz'] = b'not copied bulk'
|
||||
payload['runs/a/queue.jsonl'] = b'not copied jsonl\n'
|
||||
payload['extension-build/provenance.json'] = b'not copied build'
|
||||
rows = [row(name, raw) for name, raw in sorted(payload.items())]
|
||||
manifest = data(dict(root=v.PROJECT + '/' + v.EXPERIMENT, files=rows))
|
||||
checksums = ''.join(r['sha256'] + ' ' + r['path'] + '\n' for r in rows)
|
||||
checksums = (checksums + v.sha(manifest) + ' manifest.json\n').encode()
|
||||
all_data = {**payload, 'manifest.json': manifest, 'checksums.sha256': checksums}
|
||||
index, omitted = [], []
|
||||
for name, raw in all_data.items():
|
||||
current = row(name, raw); destination, reason = v._selection(current)
|
||||
if destination is None:
|
||||
omitted.append(dict(current, reason=reason))
|
||||
else:
|
||||
self.write(v.REGISTRY + '/' + destination, raw)
|
||||
index.append(dict(source=v.EXPERIMENT + '/' + name, copy=destination, bytes=len(raw), sha256=v.sha(raw)))
|
||||
self.write(v.REGISTRY + '/evidence-index.json', data(index))
|
||||
self.write(v.REGISTRY + '/source-identity.json', data(source_rows))
|
||||
self.write(v.REGISTRY + '/seal-verification.json', data(dict(pass_all=True, root=v.PROJECT + '/' + v.EXPERIMENT,
|
||||
manifest_sha256=v.sha(manifest), checksums_sha256=v.sha(checksums), payload_file_count=len(rows), payload_bytes=sum(r['bytes'] for r in rows))))
|
||||
directory = self.repo / v.REGISTRY
|
||||
receipt = dict(schema='phase4an-engineering-publication-v1', publication_complete=True, registry=v.REGISTRY,
|
||||
project_root=v.PROJECT, bulk_remains_at=v.EXPERIMENT, declared_status=status, model_directory_sealed=False,
|
||||
used_source_count=21, model_files_copied=False, complete_bulk_backup=False, training=False,
|
||||
candidate_generated=False, learning_pass_claimed=False, source_identity='source-identity.json',
|
||||
experiment_manifest_sha256=v.sha(manifest), experiment_checksums_sha256=v.sha(checksums),
|
||||
preregistration_sha256=v.sha(payload['protocol/preregistered.json']), final_sha256=v.sha(payload['audit/final-status.json']),
|
||||
analysis_sha256=v.sha(payload['audit/engineering-analysis.json']), copied_evidence_files=len(index),
|
||||
copied_evidence_bytes=sum(r['bytes'] for r in index), experiment_payload_not_copied=omitted,
|
||||
files=[row(path.relative_to(directory).as_posix(), path.read_bytes()) for path in sorted(directory.rglob('*'))
|
||||
if path.is_file() and path.name != 'publication-receipt.json'])
|
||||
raw = data(receipt); self.write(v.REGISTRY + '/publication-receipt.json', raw)
|
||||
return v.sha(raw)
|
||||
|
||||
def refresh_receipt(self, **updates):
|
||||
path = self.repo / v.REGISTRY / 'publication-receipt.json'
|
||||
receipt = json.loads(path.read_bytes()); receipt.update(updates)
|
||||
receipt['files'] = [row(r['path'], (self.repo / v.REGISTRY / r['path']).read_bytes()) for r in receipt['files']]
|
||||
raw = data(receipt); path.write_bytes(raw); self.pin = v.sha(raw); self.stage()
|
||||
|
||||
def test_index_and_commit_pass_without_touching_index(self):
|
||||
before = (self.repo / '.git/index').read_bytes()
|
||||
result = v.verify(self.repo, self.pin, staged=True)
|
||||
self.assertTrue(result['pass_all'])
|
||||
self.assertFalse(result['working_tree_payload_used'])
|
||||
self.assertFalse(result['experiment_bulk_verified_by_this_tool'])
|
||||
self.assertEqual(result['model_source_files'], 21)
|
||||
self.assertEqual((self.repo / '.git/index').read_bytes(), before)
|
||||
self.git('commit', '-qm', 'synthetic publication')
|
||||
committed = v.verify(self.repo, self.pin, commit='HEAD')
|
||||
self.assertEqual(committed['source'], 'commit')
|
||||
self.assertEqual(committed['commit'], self.git('rev-parse', 'HEAD').decode().strip())
|
||||
|
||||
def test_working_tree_never_substitutes_for_staged_or_committed_blobs(self):
|
||||
target = v.MODEL + '/' + v.SOURCES[0]
|
||||
self.git('commit', '-qm', 'valid synthetic')
|
||||
original = (self.repo / target).read_bytes()
|
||||
self.write(target, b'bad working tree but index is valid\n')
|
||||
self.assertTrue(v.verify(self.repo, self.pin, staged=True)['pass_all'])
|
||||
self.git('add', '--', target)
|
||||
self.write(target, original)
|
||||
with self.assertRaisesRegex(ValueError, 'SHA/bytes'):
|
||||
v.verify(self.repo, self.pin, staged=True)
|
||||
self.assertTrue(v.verify(self.repo, self.pin, commit='HEAD')['pass_all'])
|
||||
|
||||
def test_external_receipt_sha_and_exclusive_source_mode_are_required(self):
|
||||
for kwargs, pin in [({'staged': True}, '0' * 64), ({}, self.pin), ({'staged': True, 'commit': 'HEAD'}, self.pin)]:
|
||||
with self.subTest(kwargs=kwargs), self.assertRaises(ValueError):
|
||||
v.verify(self.repo, pin, **kwargs)
|
||||
|
||||
def test_extra_scientific_draft_and_registry_file_are_rejected(self):
|
||||
for name in (v.MODEL + '/PROTOCOL_DRAFT.md', v.REGISTRY + '/unlisted.json'):
|
||||
self.write(name, b'not in registered inventory\n'); self.git('add', '--', name)
|
||||
with self.subTest(name=name), self.assertRaises(ValueError):
|
||||
v.verify(self.repo, self.pin, staged=True)
|
||||
self.git('rm', '--cached', '--', name)
|
||||
(self.repo / name).unlink()
|
||||
|
||||
def test_missing_source_and_nonregular_index_entry_rejected(self):
|
||||
path = v.MODEL + '/' + v.SOURCES[0]
|
||||
self.git('rm', '--cached', '--', path)
|
||||
with self.assertRaises(ValueError):
|
||||
v.verify(self.repo, self.pin, staged=True)
|
||||
self.git('add', '--', path)
|
||||
oid = self.git('hash-object', '-w', '--stdin', input=b'elsewhere\n').decode().strip()
|
||||
self.git('update-index', '--cacheinfo', '120000,' + oid + ',' + path)
|
||||
with self.assertRaisesRegex(ValueError, 'nonregular'):
|
||||
v.verify(self.repo, self.pin, staged=True)
|
||||
|
||||
def test_source_identity_semantics_checked_even_under_new_receipt_pin(self):
|
||||
path = self.repo / v.REGISTRY / 'source-identity.json'
|
||||
identities = json.loads(path.read_bytes()); identities[0]['sha256'] = 'f' * 64
|
||||
path.write_bytes(data(identities)); self.refresh_receipt()
|
||||
with self.assertRaisesRegex(ValueError, 'Source/snapshot'):
|
||||
v.verify(self.repo, self.pin, staged=True)
|
||||
|
||||
def test_evidence_index_source_and_escaping_receipt_paths_fail(self):
|
||||
path = self.repo / v.REGISTRY / 'evidence-index.json'
|
||||
index = json.loads(path.read_bytes()); index[0]['source'] = 'wrong/source.py'
|
||||
path.write_bytes(data(index)); self.refresh_receipt()
|
||||
with self.assertRaisesRegex(ValueError, 'Evidence index'):
|
||||
v.verify(self.repo, self.pin, staged=True)
|
||||
receipt_path = self.repo / v.REGISTRY / 'publication-receipt.json'
|
||||
receipt = json.loads(receipt_path.read_bytes()); receipt['files'][0]['path'] = '../escape.json'
|
||||
raw = data(receipt); receipt_path.write_bytes(raw); self.pin = v.sha(raw); self.stage()
|
||||
with self.assertRaisesRegex(ValueError, 'escaping'):
|
||||
v.verify(self.repo, self.pin, staged=True)
|
||||
|
||||
def test_checksums_manifest_self_hash_is_not_just_trusted(self):
|
||||
path = self.repo / v.REGISTRY / 'evidence/checksums.sha256'
|
||||
text = path.read_text(); lines = text.splitlines(); lines[-1] = '0' * 64 + ' manifest.json'
|
||||
raw = ('\n'.join(lines) + '\n').encode(); path.write_bytes(raw)
|
||||
self.refresh_receipt(experiment_checksums_sha256=v.sha(raw))
|
||||
with self.assertRaisesRegex(ValueError, 'Checksum inventory'):
|
||||
v.verify(self.repo, self.pin, staged=True)
|
||||
|
||||
def test_declared_fail_status_is_preserved_not_relabelled_science_pass(self):
|
||||
self.pin = self.fixture(status='FAIL-NUMERICAL'); self.stage()
|
||||
result = v.verify(self.repo, self.pin, staged=True)
|
||||
self.assertTrue(result['pass_all'])
|
||||
self.assertEqual(result['declared_status'], 'FAIL-NUMERICAL')
|
||||
self.assertTrue(result['publication_integrity_is_not_scientific_pass'])
|
||||
self.assertFalse(result['scientific_gate_executed'])
|
||||
|
||||
def test_duplicate_JSON_field_in_receipt_is_rejected(self):
|
||||
path = self.repo / v.REGISTRY / 'publication-receipt.json'
|
||||
raw = path.read_bytes().replace(b'{', b'{"publication_complete":true,', 1)
|
||||
path.write_bytes(raw); self.pin = v.sha(raw); self.stage()
|
||||
with self.assertRaisesRegex(ValueError, 'Duplicate JSON'):
|
||||
v.verify(self.repo, self.pin, staged=True)
|
||||
|
||||
def test_cli_emits_compact_summary_without_full_payload(self):
|
||||
out = io.StringIO()
|
||||
with contextlib.redirect_stdout(out):
|
||||
status = v.main(['--repo', str(self.repo), '--receipt-sha256', self.pin, '--staged'])
|
||||
self.assertEqual(status, 0)
|
||||
result = json.loads(out.getvalue())
|
||||
self.assertTrue(result['pass_all'])
|
||||
self.assertNotIn('verified_blobs', result)
|
||||
self.assertLess(len(out.getvalue()), 1800)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,282 @@
|
||||
"""Verify AN publication bytes in Git's index or one immutable commit.
|
||||
|
||||
No working-tree payload fallback, Git writes, network, bulk verification or
|
||||
scientific execution. The publication receipt SHA must be externally trusted.
|
||||
Only the 21 registered engineering sources are allowed in the AN model scope.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path, PurePosixPath
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
PROJECT = '/data/digital-drosophila'
|
||||
MODEL = 'models/male-cns-lif/olfactory-conditioning-lab-v1'
|
||||
EXPERIMENT = 'experiments/phase4an-engineering-validation'
|
||||
REGISTRY = 'registry/phase4an-engineering-validation'
|
||||
MODULES = ('runtime_light', 'runtime_continuation', 'engineering_inputs', 'engineering_plan',
|
||||
'engineering_compare', 'engineering_queue_audit', 'engineering_prepare',
|
||||
'engineering_workflow', 'engineering_scopes', 'engineering_analysis')
|
||||
SOURCES = tuple(sorted([m + '.py' for m in MODULES] + ['test_' + m + '.py' for m in MODULES] + ['ENGINEERING_GATE_DRAFT.md']))
|
||||
MAX_BYTES = 4 * 1024**2
|
||||
SUFFIXES = frozenset(('.json', '.csv', '.md', '.log', '.sha256'))
|
||||
CONTROL = frozenset(('manifest.json', 'checksums.sha256'))
|
||||
SIDE = frozenset(('evidence-index.json', 'source-identity.json', 'seal-verification.json'))
|
||||
SHA = re.compile(r'[0-9a-f]{64}\Z')
|
||||
OID = re.compile(r'(?:[0-9a-f]{40}|[0-9a-f]{64})\Z')
|
||||
STATUSES = frozenset(('PASS-EXACT-LIGHT-CONTINUATION-ENGINEERING-NOT-LEARNING', 'FAIL-NUMERICAL', 'INCOMPLETE-COVERAGE'))
|
||||
|
||||
|
||||
def require(value, message):
|
||||
if not value:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def sha(data):
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def relative(name):
|
||||
require(type(name) is str and name and not any(c in name for c in '\\\x00\r\n\t'), 'Invalid relative path')
|
||||
path = PurePosixPath(name)
|
||||
require(not path.is_absolute() and str(path) == name and all(p not in ('', '.', '..') and ':' not in p for p in path.parts),
|
||||
'Noncanonical or escaping path: ' + name)
|
||||
return name
|
||||
|
||||
|
||||
def parse(data):
|
||||
def pairs(rows):
|
||||
result = {}
|
||||
for name, value in rows:
|
||||
require(name not in result, 'Duplicate JSON field: ' + name)
|
||||
result[name] = value
|
||||
return result
|
||||
return json.loads(data.decode('utf-8'), object_pairs_hook=pairs,
|
||||
parse_constant=lambda value: (_ for _ in ()).throw(ValueError('Nonfinite JSON: ' + value)))
|
||||
|
||||
|
||||
def inventory(rows):
|
||||
require(type(rows) is list and rows, 'Missing payload inventory')
|
||||
indexed = {}
|
||||
for row in rows:
|
||||
require(type(row) is dict and set(row) == {'path', 'bytes', 'sha256'}, 'Invalid inventory row schema')
|
||||
path = relative(row['path'])
|
||||
require(path not in indexed and '__pycache__' not in PurePosixPath(path).parts and
|
||||
type(row['bytes']) is int and row['bytes'] >= 0 and type(row['sha256']) is str and SHA.fullmatch(row['sha256']),
|
||||
'Invalid/duplicate inventory row: ' + path)
|
||||
indexed[path] = row
|
||||
return indexed
|
||||
|
||||
|
||||
class GitBlobs:
|
||||
def __init__(self, repo, staged, commit):
|
||||
require(type(staged) is bool and staged != (commit is not None), 'Select exactly one of staged or commit')
|
||||
self.repo = Path(repo).resolve(strict=True)
|
||||
env = os.environ.copy()
|
||||
for key in ('GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_COMMON_DIR', 'GIT_OBJECT_DIRECTORY', 'GIT_ALTERNATE_OBJECT_DIRECTORIES'):
|
||||
env.pop(key, None)
|
||||
env.update(GIT_OPTIONAL_LOCKS='0', GIT_NO_REPLACE_OBJECTS='1', GIT_NO_LAZY_FETCH='1')
|
||||
self.env = env
|
||||
self.base = ['git', '--no-optional-locks', '-c', 'core.fsmonitor=false', '-c', 'gc.auto=0', '-C', str(self.repo)]
|
||||
require(Path(self.command('rev-parse', '--show-toplevel').decode().strip()).resolve() == self.repo, 'Supply repository top-level')
|
||||
self.staged, self.commit = staged, None
|
||||
if not staged:
|
||||
require(type(commit) is str and commit and not commit.startswith('-') and not any(c in commit for c in '\x00\r\n'), 'Invalid commit selector')
|
||||
self.commit = self.command('rev-parse', '--verify', '--end-of-options', commit + '^{commit}').decode('ascii').strip()
|
||||
require(OID.fullmatch(self.commit), 'Unresolved commit identity')
|
||||
self.snapshot = self.listing()
|
||||
self.entries, self.verified = {}, {}
|
||||
require(self.snapshot.endswith(b'\0'), 'Empty or malformed scoped Git inventory')
|
||||
for raw in self.snapshot[:-1].split(b'\0'):
|
||||
metadata, path = raw.split(b'\t', 1); fields = metadata.decode('ascii').split(' ')
|
||||
require(len(fields) == 3, 'Malformed Git inventory entry')
|
||||
mode, second, third = fields
|
||||
oid = second if staged else third
|
||||
require((third == '0' if staged else second == 'blob') and mode in ('100644', '100755') and OID.fullmatch(oid),
|
||||
'Unmerged, symlink, gitlink or nonregular blob is forbidden')
|
||||
name = relative(path.decode('utf-8'))
|
||||
require(name.startswith(MODEL + '/') or name.startswith(REGISTRY + '/'), 'Out-of-scope Git blob')
|
||||
require(name not in self.entries, 'Duplicate Git path')
|
||||
self.entries[name] = (mode, oid)
|
||||
self.batch = subprocess.Popen(self.base + ['cat-file', '--batch'], env=self.env,
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
def command(self, *args):
|
||||
result = subprocess.run(self.base + list(args), env=self.env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
require(result.returncode == 0, 'Git read failed: ' + result.stderr.decode('utf-8', 'replace').strip())
|
||||
return result.stdout
|
||||
|
||||
def listing(self):
|
||||
return (self.command('ls-files', '--stage', '--full-name', '-z', '--', MODEL, REGISTRY) if self.staged else
|
||||
self.command('ls-tree', '-r', '-z', '--full-tree', self.commit, '--', MODEL, REGISTRY))
|
||||
|
||||
def read(self, path, expected=None):
|
||||
require(path in self.entries, 'Missing Git blob: ' + path)
|
||||
mode, oid = self.entries[path]
|
||||
self.batch.stdin.write((oid + '\n').encode('ascii')); self.batch.stdin.flush()
|
||||
fields = self.batch.stdout.readline(256).decode('ascii').strip().split(' ')
|
||||
require(len(fields) == 3 and fields[:2] == [oid, 'blob'] and fields[2].isdigit(), 'Invalid Git blob response')
|
||||
size = int(fields[2]); require(size <= MAX_BYTES, 'Selected Git blob exceeds 4 MiB: ' + path)
|
||||
data = self.batch.stdout.read(size)
|
||||
require(len(data) == size and self.batch.stdout.read(1) == b'\n', 'Truncated Git object')
|
||||
if expected is not None:
|
||||
require(size == expected['bytes'] and sha(data) == expected['sha256'], 'Git blob SHA/bytes mismatch: ' + path)
|
||||
self.verified[path] = dict(path=path, bytes=size, sha256=sha(data), git_oid=oid, git_mode=mode)
|
||||
return data
|
||||
|
||||
def close(self):
|
||||
# On a malformed/oversize object the child might still be writing.
|
||||
# Terminating this read-only batch process cannot alter the repository.
|
||||
if self.batch.poll() is None:
|
||||
self.batch.terminate()
|
||||
self.batch.communicate(timeout=10)
|
||||
|
||||
|
||||
def _seal(manifest_bytes, checksum_bytes, receipt):
|
||||
require(sha(manifest_bytes) == receipt['experiment_manifest_sha256'] and
|
||||
sha(checksum_bytes) == receipt['experiment_checksums_sha256'], 'Receipt-bound seal control mismatch')
|
||||
manifest = parse(manifest_bytes)
|
||||
require(type(manifest) is dict and set(manifest) == {'root', 'files'} and manifest['root'] == PROJECT + '/' + EXPERIMENT,
|
||||
'Wrong experiment seal root/schema')
|
||||
rows = inventory(manifest['files']); require(not (set(rows) & CONTROL), 'Seal controls cannot be payload rows')
|
||||
checks = {}
|
||||
for line in checksum_bytes.decode('utf-8').splitlines():
|
||||
require(len(line) >= 67 and SHA.fullmatch(line[:64]) and line[64:66] == ' ', 'Malformed checksum row')
|
||||
name = relative(line[66:]); require(name not in checks, 'Duplicate checksum row'); checks[name] = line[:64]
|
||||
require(checks == {**{k: v['sha256'] for k, v in rows.items()}, 'manifest.json': sha(manifest_bytes)},
|
||||
'Checksum inventory or manifest self-SHA mismatch')
|
||||
return rows
|
||||
|
||||
|
||||
def _selection(row):
|
||||
path = PurePosixPath(row['path'])
|
||||
if path.parts[0] == 'extension-build':
|
||||
return None, 'build_artifact'
|
||||
if row['bytes'] > MAX_BYTES:
|
||||
return None, 'exceeds_4_MiB'
|
||||
if path.parts[:2] == ('protocol', 'source-snapshot') and len(path.parts) == 3 and path.name in SOURCES:
|
||||
return 'source-snapshot/' + path.name, None
|
||||
if path.suffix.lower() not in SUFFIXES:
|
||||
return None, 'bulk_binary_or_unselected_suffix_including_jsonl'
|
||||
return 'evidence/' + row['path'], None
|
||||
|
||||
|
||||
def verify(repo, receipt_sha, *, staged=False, commit=None):
|
||||
require(type(receipt_sha) is str and SHA.fullmatch(receipt_sha), 'Externally trusted receipt SHA-256 is mandatory')
|
||||
git = GitBlobs(repo, staged, commit)
|
||||
try:
|
||||
raw = git.read(REGISTRY + '/publication-receipt.json')
|
||||
require(sha(raw) == receipt_sha, 'External receipt SHA-256 mismatch')
|
||||
receipt = parse(raw)
|
||||
require(receipt.get('schema') == 'phase4an-engineering-publication-v1' and receipt.get('publication_complete') is True and
|
||||
receipt.get('registry') == REGISTRY and receipt.get('project_root') == PROJECT and receipt.get('bulk_remains_at') == EXPERIMENT and
|
||||
receipt.get('declared_status') in STATUSES and receipt.get('model_directory_sealed') is False and
|
||||
type(receipt.get('used_source_count')) is int and receipt['used_source_count'] == 21 and
|
||||
receipt.get('model_files_copied') is False and receipt.get('complete_bulk_backup') is False and
|
||||
receipt.get('training') is False and receipt.get('candidate_generated') is False and
|
||||
receipt.get('learning_pass_claimed') is False, 'Wrong AN publication scope or declarations')
|
||||
rows = inventory(receipt['files']); require('publication-receipt.json' not in rows, 'Receipt cannot self-hash')
|
||||
actual = {p[len(REGISTRY) + 1:] for p in git.entries if p.startswith(REGISTRY + '/')}
|
||||
require(actual == set(rows) | {'publication-receipt.json'}, 'Registry has unlisted/missing Git files')
|
||||
required = SIDE | {'evidence/manifest.json', 'evidence/checksums.sha256', 'evidence/protocol/preregistered.json',
|
||||
'evidence/audit/final-status.json', 'evidence/audit/engineering-analysis.json'}
|
||||
require(required <= rows.keys(), 'Missing registry controls/evidence')
|
||||
retained = {}
|
||||
for name, row in rows.items():
|
||||
require(row['bytes'] <= MAX_BYTES, 'Registry file exceeds publication cap')
|
||||
data = git.read(REGISTRY + '/' + name, row)
|
||||
if name in required:
|
||||
retained[name] = data
|
||||
payload = _seal(retained['evidence/manifest.json'], retained['evidence/checksums.sha256'], receipt)
|
||||
require({name for name in payload if name.startswith('protocol/source-snapshot/')} ==
|
||||
{'protocol/source-snapshot/' + name for name in SOURCES}, 'Sealed source snapshots are not exactly the 21 used sources')
|
||||
controls = {name: dict(path=name, bytes=len(retained['evidence/' + name]), sha256=sha(retained['evidence/' + name])) for name in CONTROL}
|
||||
expected_index, omitted = {}, []
|
||||
for row in [*payload.values(), *controls.values()]:
|
||||
destination, reason = _selection(row)
|
||||
if destination is None:
|
||||
omitted.append(dict(row, reason=reason))
|
||||
else:
|
||||
require(destination not in expected_index, 'Duplicate publication destination')
|
||||
expected_index[destination] = dict(source=EXPERIMENT + '/' + row['path'], copy=destination, bytes=row['bytes'], sha256=row['sha256'])
|
||||
indexed = {}
|
||||
index = parse(retained['evidence-index.json']); require(type(index) is list, 'Malformed evidence index')
|
||||
for row in index:
|
||||
require(type(row) is dict and set(row) == {'source', 'copy', 'bytes', 'sha256'}, 'Invalid evidence-index row')
|
||||
require(type(row['bytes']) is int and row['bytes'] >= 0 and type(row['sha256']) is str and SHA.fullmatch(row['sha256']), 'Invalid evidence-index size/hash')
|
||||
name = relative(row['copy']); relative(row['source']); require(name not in indexed, 'Duplicate evidence-index destination')
|
||||
indexed[name] = row
|
||||
require(indexed == expected_index and set(rows) == set(indexed) | SIDE, 'Evidence index does not match seal/selection/receipt')
|
||||
for name, row in indexed.items():
|
||||
require(rows[name] == dict(path=name, bytes=row['bytes'], sha256=row['sha256']), 'Evidence receipt row mismatch')
|
||||
require(type(receipt.get('copied_evidence_files')) is int and receipt['copied_evidence_files'] == len(indexed) and
|
||||
type(receipt.get('copied_evidence_bytes')) is int and receipt['copied_evidence_bytes'] == sum(r['bytes'] for r in indexed.values()) and
|
||||
sorted(receipt.get('experiment_payload_not_copied', []), key=lambda r: r['path']) == sorted(omitted, key=lambda r: r['path']),
|
||||
'Copied/omitted payload accounting mismatch')
|
||||
proof = parse(retained['seal-verification.json'])
|
||||
require(proof.get('pass_all') is True and proof.get('root') == PROJECT + '/' + EXPERIMENT and
|
||||
proof.get('manifest_sha256') == receipt['experiment_manifest_sha256'] and
|
||||
proof.get('checksums_sha256') == receipt['experiment_checksums_sha256'] and
|
||||
proof.get('payload_file_count') == len(payload) and proof.get('payload_bytes') == sum(r['bytes'] for r in payload.values()),
|
||||
'Seal-verification sidecar contradicts controls')
|
||||
reg = parse(retained['evidence/protocol/preregistered.json'])
|
||||
require(reg.get('schema') == 'phase4an-engineering-registration-v1' and reg.get('training') is False and
|
||||
sha(retained['evidence/protocol/preregistered.json']) == receipt['preregistration_sha256'], 'Registration anchor mismatch')
|
||||
identities = parse(retained['source-identity.json'])
|
||||
require(type(identities) is list and len(identities) == 21 and receipt.get('source_identity') == 'source-identity.json', 'Missing source identity inventory')
|
||||
require(set(reg['source_sha256']) == {PROJECT + '/' + MODEL + '/' + name for name in SOURCES}, 'Registration does not bind exactly 21 engineering sources')
|
||||
observed_model = {p[len(MODEL) + 1:] for p in git.entries if p.startswith(MODEL + '/')}
|
||||
require(observed_model == set(SOURCES), 'Model Git scope must contain exactly the 21 registered used sources, not drafts')
|
||||
by_copy = {}
|
||||
for row in identities:
|
||||
require(type(row) is dict and set(row) == {'model_source', 'sealed_source', 'copy', 'bytes', 'sha256'}, 'Invalid source identity row')
|
||||
require(type(row['bytes']) is int and row['bytes'] >= 0 and type(row['sha256']) is str and SHA.fullmatch(row['sha256']), 'Invalid source identity size/hash')
|
||||
name = relative(row['copy']); require(name not in by_copy, 'Duplicate source identity'); by_copy[name] = row
|
||||
require(set(by_copy) == {'source-snapshot/' + name for name in SOURCES}, 'Source identity coverage mismatch')
|
||||
for name in SOURCES:
|
||||
copy_name, sealed = 'source-snapshot/' + name, 'protocol/source-snapshot/' + name
|
||||
row, manifest_row = by_copy[copy_name], payload[sealed]
|
||||
expected = dict(model_source=MODEL + '/' + name, sealed_source=EXPERIMENT + '/' + sealed,
|
||||
copy=copy_name, bytes=manifest_row['bytes'], sha256=manifest_row['sha256'])
|
||||
require(row == expected and rows[copy_name] == dict(path=copy_name, bytes=row['bytes'], sha256=row['sha256']) and
|
||||
reg['source_sha256'][PROJECT + '/' + MODEL + '/' + name] == row['sha256'] and
|
||||
reg['input_sha256'].get(PROJECT + '/' + EXPERIMENT + '/' + sealed) == row['sha256'], 'Source/snapshot/registration disagreement: ' + name)
|
||||
git.read(MODEL + '/' + name, row)
|
||||
for name, key in [('audit/final-status.json', 'final_sha256'), ('audit/engineering-analysis.json', 'analysis_sha256')]:
|
||||
require(sha(retained['evidence/' + name]) == receipt[key], 'Final/analysis anchor mismatch')
|
||||
require(parse(retained['evidence/' + name]).get('status') == receipt['declared_status'], 'Declared status disagreement')
|
||||
require(git.listing() == git.snapshot, 'Scoped Git inventory changed during verification')
|
||||
return dict(pass_all=True, verification='AN actual Git blob publication integrity', source='staged-index' if staged else 'commit',
|
||||
commit=git.commit, receipt_sha256=receipt_sha, declared_status=receipt['declared_status'], model_source_files=21,
|
||||
registry_files=len(rows) + 1, verified_blob_count=len(git.verified), verified_bytes=sum(v['bytes'] for v in git.verified.values()),
|
||||
scoped_git_inventory_sha256=sha(git.snapshot), working_tree_payload_used=False, git_index_modified=False,
|
||||
experiment_bulk_verified_by_this_tool=False, scientific_gate_executed=False, model_directory_declared_sealed=False,
|
||||
publication_integrity_is_not_scientific_pass=True)
|
||||
finally:
|
||||
git.close()
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--repo', required=True)
|
||||
parser.add_argument('--receipt-sha256', required=True)
|
||||
source = parser.add_mutually_exclusive_group(required=True)
|
||||
source.add_argument('--staged', action='store_true'); source.add_argument('--commit')
|
||||
args = parser.parse_args(argv)
|
||||
try:
|
||||
result = verify(args.repo, args.receipt_sha256, staged=args.staged, commit=args.commit)
|
||||
except (ValueError, KeyError, TypeError, AttributeError, OSError, UnicodeError, EOFError) as error:
|
||||
print(json.dumps(dict(pass_all=False, error_type=type(error).__name__, error=str(error), scientific_gate_executed=False)))
|
||||
return 1
|
||||
print(json.dumps(result, ensure_ascii=False, allow_nan=False))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user