Seal AO interface failure and verified isolated postprocessing diagnosis
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
"""One fresh-process, CPU-only diagnosis of the stopped AO postprocessor.
|
||||
|
||||
Consumes the existing sealed first trajectory; never executes a neural trial,
|
||||
changes the stopped protocol, or installs a delta. Diagnostic files are new.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import time
|
||||
|
||||
ROOT = Path('/data/digital-drosophila')
|
||||
EXP = ROOT / 'experiments/phase4ao-conditioning-interface'
|
||||
MODEL = ROOT / 'models/male-cns-lif/olfactory-conditioning-lab-v1'
|
||||
DIAG = ROOT / 'experiments/phase4ao-postprocess-isolation'
|
||||
E_SHA = '757935a4815d9f625640df685184ed3fe44a8845323562796da7aa43cbb194e1'
|
||||
|
||||
|
||||
def digest(p):
|
||||
h = hashlib.sha256()
|
||||
with Path(p).open('rb') as f:
|
||||
for data in iter(lambda: f.read(8 << 20), b''): h.update(data)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def main(reg_sha, checkpoint_sha, authority_sha, stopped_sha):
|
||||
assert sys.dont_write_bytecode
|
||||
assert not any(k in sys.modules for k in ('runtime_light', 'runtime_continuation', 'phase4an_sealed_am_overlay'))
|
||||
regpath = EXP / 'protocol/preregistered.json'
|
||||
authority = EXP / 'receipts/D1--reference--epoch-000.authority.json'
|
||||
stopped = EXP / 'audit/stopped.json'
|
||||
checkpoint = EXP / 'runs/D1--reference--epoch-000/checkpoint-manifest.json'
|
||||
pins = {regpath: reg_sha, authority: authority_sha, stopped: stopped_sha, checkpoint: checkpoint_sha,
|
||||
MODEL / 'conditioning_epoch.py': E_SHA}
|
||||
assert all(digest(p) == h for p, h in pins.items())
|
||||
reg = json.loads(regpath.read_text())
|
||||
assert all(digest(p) == h for field in ('source_sha256', 'input_sha256', 'prepared_file_sha256') for p, h in reg[field].items())
|
||||
value = json.loads(authority.read_text())
|
||||
row = reg['plan']['calls'][0]
|
||||
assert row['row_id'] == 'D1--reference--epoch-000' and value['epoch_id'] == row['epoch_id']
|
||||
assert value['checkpoint_manifest_sha256'] == checkpoint_sha
|
||||
assert value['command'] == dict(steps=1200, compartment=reg['plan']['compartment_verbatim'], us_tick=900,
|
||||
d_command=1.0, frozen=False, trace_backend='torch')
|
||||
out = DIAG / 'postprocess'
|
||||
receipt = DIAG / 'diagnostic-receipt.json'
|
||||
assert DIAG.is_dir() and not out.exists() and not receipt.exists()
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = ''
|
||||
sys.path.insert(0, str(MODEL))
|
||||
import conditioning_epoch as E
|
||||
assert digest(E.__file__) == E_SHA
|
||||
started = time.perf_counter()
|
||||
chain = E.postprocess_epoch(authority, authority_sha, out)
|
||||
update = json.loads((out / 'update-receipt.json').read_text())
|
||||
assert all(digest(p) == h for p, h in pins.items())
|
||||
result = dict(status='PASS-ISOLATED-POSTPROCESS-DIAGNOSTIC-ONLY', registration_sha256=reg_sha,
|
||||
authority_sha256=authority_sha, checkpoint_manifest_sha256=checkpoint_sha, stopped_sha256=stopped_sha,
|
||||
diagnostic_script_sha256=digest(__file__), postprocess_source_sha256=E_SHA,
|
||||
result_files_sha256={str(p): digest(p) for p in out.iterdir()}, counts=update['counts'],
|
||||
before_overlay_sha256=chain['before_overlay_file_sha256'], after_overlay_sha256=chain['after_overlay_file_sha256'],
|
||||
wall_seconds=time.perf_counter() - started, CUDA_VISIBLE_DEVICES='',
|
||||
new_brain_runs=0, old_trajectory_rerun=False, weights_installed=False,
|
||||
original_protocol_remains_stopped=True, original_12_call_gate_passed=False,
|
||||
learning_success_claimed=False, candidate_generated=False,
|
||||
interpretation='The same pinned E succeeds in a fresh CPU process without the competing ordinary runtime loader cache. This is post-hoc diagnosis, not completion of AO.')
|
||||
with receipt.open('x', encoding='utf-8') as f:
|
||||
json.dump(result, f, sort_keys=True, indent=2, allow_nan=False); f.write('\n')
|
||||
print(json.dumps(dict(status=result['status'], receipt_sha256=digest(receipt), counts=result['counts'])), flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
p = argparse.ArgumentParser()
|
||||
for name in ('registration-sha256', 'checkpoint-sha256', 'authority-sha256', 'stopped-sha256'):
|
||||
p.add_argument('--' + name, required=True)
|
||||
a = p.parse_args()
|
||||
main(a.registration_sha256, a.checkpoint_sha256, a.authority_sha256, a.stopped_sha256)
|
||||
@@ -0,0 +1,128 @@
|
||||
"""Additive failure sealing, never an AO execution retry or scientific PASS."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
ROOT = Path('/data/digital-drosophila')
|
||||
EXP = ROOT / 'experiments/phase4ao-conditioning-interface'
|
||||
DIAG = ROOT / 'experiments/phase4ao-postprocess-isolation'
|
||||
MODEL = ROOT / 'models/male-cns-lif/olfactory-conditioning-lab-v1'
|
||||
|
||||
|
||||
def digest(p):
|
||||
h = hashlib.sha256()
|
||||
with Path(p).open('rb') as f:
|
||||
for b in iter(lambda: f.read(8 << 20), b''): h.update(b)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def read(p): return json.loads(Path(p).read_text())
|
||||
|
||||
|
||||
def save(p, value):
|
||||
with Path(p).open('x', encoding='utf-8') as f:
|
||||
json.dump(value, f, sort_keys=True, indent=2, allow_nan=False); f.write('\n')
|
||||
|
||||
|
||||
def seal(directory, helper):
|
||||
assert not (directory / 'manifest.json').exists() and not (directory / 'checksums.sha256').exists()
|
||||
paths = sorted(directory.rglob('*'))
|
||||
assert not any(p.is_symlink() or '__pycache__' in p.parts for p in paths)
|
||||
rows = [dict(path=p.relative_to(directory).as_posix(), bytes=p.stat().st_size, sha256=digest(p))
|
||||
for p in paths if p.is_file()]
|
||||
save(directory / 'manifest.json', dict(root=str(directory), files=rows))
|
||||
with (directory / 'checksums.sha256').open('x', encoding='utf-8', newline='\n') as f:
|
||||
f.writelines(r['sha256'] + ' ' + r['path'] + '\n' for r in rows)
|
||||
f.write(digest(directory / 'manifest.json') + ' manifest.json\n')
|
||||
return helper.verify_seal(directory)
|
||||
|
||||
|
||||
def main(reg_sha, checkpoint_sha, stopped_sha, diagnostic_sha):
|
||||
assert sys.dont_write_bytecode
|
||||
assert not (EXP / 'manifest.json').exists() and not (EXP / 'audit/final-status.json').exists()
|
||||
assert digest(EXP / 'audit/stopped.json') == stopped_sha
|
||||
assert digest(DIAG / 'diagnostic-receipt.json') == diagnostic_sha
|
||||
sys.path.insert(0, str(MODEL))
|
||||
import conditioning_preflight_prepare as P
|
||||
import runtime_continuation as C
|
||||
reg = P.read_registration(reg_sha)
|
||||
assert reg['plan']['total_calls'] == 12
|
||||
row_id = reg['plan']['calls'][0]['row_id']
|
||||
assert row_id == 'D1--reference--epoch-000'
|
||||
assert sorted(p.name for p in (EXP / 'runs').iterdir()) == [row_id]
|
||||
assert not any((EXP / 'updates').iterdir())
|
||||
assert not (EXP / 'audit/execution-complete.json').exists()
|
||||
assert sorted(p.name for p in (EXP / 'receipts').glob('*.process.json')) == ['input-generation.process.json']
|
||||
stopped = read(EXP / 'audit/stopped.json')
|
||||
assert stopped['completed_rows'] == [] and stopped['registration_sha256'] == reg_sha
|
||||
stderr = (EXP / 'logs' / (row_id + '.stderr.log')).read_text()
|
||||
assert 'E.postprocess_epoch' in stderr and 'Existing unverified module binding: phase4an_sealed_am_overlay' in stderr
|
||||
checkpoint = C.load_checkpoint(EXP / 'runs' / row_id, checkpoint_sha)
|
||||
assert checkpoint['fresh'] and checkpoint['steps'] == 1200 and checkpoint['summary']['all_finite']
|
||||
diagnostic = read(DIAG / 'diagnostic-receipt.json')
|
||||
assert diagnostic['status'] == 'PASS-ISOLATED-POSTPROCESS-DIAGNOSTIC-ONLY'
|
||||
assert diagnostic['registration_sha256'] == reg_sha and diagnostic['checkpoint_manifest_sha256'] == checkpoint_sha
|
||||
assert diagnostic['stopped_sha256'] == stopped_sha and diagnostic['new_brain_runs'] == 0
|
||||
assert diagnostic['weights_installed'] is False and diagnostic['original_protocol_remains_stopped'] is True
|
||||
P.verify_pins(diagnostic['result_files_sha256'])
|
||||
original = P.P.read_registration(P.AN_REG)
|
||||
helper = P.P.load_module('phase4ao_failure_original_seal', ROOT / P.P.AI_HELPER_REL, P.P.AI_HELPER_SHA)
|
||||
# Recheck previous complete seals without rerunning their neural trials.
|
||||
save(EXP / 'regression/AN-seal-after.json', helper.verify_seal(P.AN))
|
||||
save(EXP / 'regression/AM-seals-after.json', P.P.verify_authority(original['anchors']))
|
||||
old = P.P.verify_old_integrity(original)
|
||||
save(EXP / 'regression/old-integrity-after.json', old)
|
||||
P.read_registration(reg_sha)
|
||||
C.load_checkpoint(EXP / 'runs' / row_id, checkpoint_sha)
|
||||
source = Path(__file__).resolve().parent
|
||||
for name in ('diagnose_postprocess_isolation.py', 'test_postprocess_isolation.py', 'finalize_failed_phase4ao.py'):
|
||||
target = DIAG / name
|
||||
assert not target.exists()
|
||||
shutil.copyfile(source / name, target)
|
||||
diag_seal = seal(DIAG, helper)
|
||||
summary = checkpoint['summary']
|
||||
analysis = dict(status='FAIL-POSTPROCESS-MODULE-IDENTITY', registration_sha256=reg_sha,
|
||||
planned_brain_calls=12, actual_complete_brain_trajectories=1, complete_workflow_rows=0,
|
||||
executed_biological_ms=120, executed_ticks=1200, stopped_sha256=stopped_sha,
|
||||
first_checkpoint_manifest_sha256=checkpoint_sha,
|
||||
failure_stage='after neural runtime and immediate seal, before epoch delta output creation',
|
||||
cause='Two separately loaded frozen runtime_light module objects use private _LOADED caches but share a sys.modules helper name; the second correctly rejects unowned existing binding.',
|
||||
previous_synthetic_test_gap='Tests covered isolated loader and synthetic overlay fixture, not the mixed actual loader integration.',
|
||||
unchanged_protection='No loader cache or frozen source was patched or bypassed.',
|
||||
runtime=dict(neurons=summary['nodes'], edges=summary['edges'], spikes=summary['total_spikes'],
|
||||
all_finite=summary['all_finite'], simulation_seconds=summary['diagnostic_simulation_seconds'],
|
||||
wall_seconds=summary['wall_seconds'], build_or_cache_seconds=summary['build_or_cache_seconds'],
|
||||
peak_allocated_vram_bytes=summary['peak_allocated_vram_bytes']),
|
||||
diagnostic=dict(receipt_sha256=diagnostic_sha, experiment=str(DIAG), manifest_sha256=diag_seal['manifest_sha256'],
|
||||
status=diagnostic['status'], actual_changed_edges=diagnostic['counts']['actual_bitwise_changed_edges'],
|
||||
off_compartment_changed_edges=diagnostic['counts']['actual_off_compartment_bitwise_changed_edges'],
|
||||
weights_installed=False, old_trajectory_rerun=False, new_brain_runs=0),
|
||||
old_integrity_roots=len(old), old_integrity_pass=True, prior_AN_AM_seals_preserved=True,
|
||||
original_sources_unchanged=True, scientific_learning_assessed=False, learning_success_claimed=False,
|
||||
full_conditioning_executed=False, learned_candidate=False,
|
||||
next_step='Separately register a new process-isolated coordinator. Do not overwrite or resume this stopped AO inventory; no result-selected seed or brain parameter change.')
|
||||
save(EXP / 'audit/failure-analysis.json', analysis)
|
||||
final = dict(status=analysis['status'], analysis_path='audit/failure-analysis.json',
|
||||
analysis_sha256=digest(EXP / 'audit/failure-analysis.json'), registration_sha256=reg_sha,
|
||||
planned_calls=12, actual_complete_brain_trajectories=1, completed_calls=0,
|
||||
old_integrity_roots=len(old), old_integrity_pass=True, prior_AN_AM_seals_preserved=True,
|
||||
frozen_21_and_previous_draft_20_unchanged=True, registered_51_sources_unchanged=True,
|
||||
learned_candidate=False, learning_success_claimed=False, full_conditioning_executed=False,
|
||||
old_trajectory_rerun=False, original_failure_preserved=True, model_directory_sealed=False,
|
||||
five_reference_trials='Sealed AM evidence retained; not rerun by AO or the CPU diagnosis.',
|
||||
finalizer_source_sha256=digest(__file__), diagnostic_manifest_sha256=diag_seal['manifest_sha256'])
|
||||
save(EXP / 'audit/final-status.json', final)
|
||||
verified = seal(EXP, helper)
|
||||
print(json.dumps(dict(status=final['status'], manifest_sha256=verified['manifest_sha256'],
|
||||
final_sha256=digest(EXP / 'audit/final-status.json'), diagnostic_manifest_sha256=diag_seal['manifest_sha256'])), flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
p = argparse.ArgumentParser()
|
||||
for name in ('registration-sha256', 'checkpoint-sha256', 'stopped-sha256', 'diagnostic-sha256'):
|
||||
p.add_argument('--' + name, required=True)
|
||||
a = p.parse_args()
|
||||
main(a.registration_sha256, a.checkpoint_sha256, a.stopped_sha256, a.diagnostic_sha256)
|
||||
@@ -0,0 +1,291 @@
|
||||
"""Publish the sealed AO interface evidence, not a backup or learning verdict.
|
||||
|
||||
All three external anchors are mandatory. The original complete experiment is
|
||||
verified before and after copying; only this new exclusive registry is written.
|
||||
The registry then receives its own independently verified small-file seal.
|
||||
"""
|
||||
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/phase4ao-conditioning-interface')
|
||||
REGISTRY_REL = Path('registry/phase4ao-conditioning-interface')
|
||||
HELPER_REL = Path('models/male-cns-lif/pn-recruitment-audit-v1/publication.py')
|
||||
HELPER_SHA = '7a79c499098b74163e7bb16adabcbc54e7069dafa204d0b99c7f4c03d79f8016'
|
||||
REGISTRATION = 'protocol/preregistered.json'
|
||||
FINAL = 'audit/final-status.json'
|
||||
ANALYSIS = 'audit/interface-analysis.json'
|
||||
FAILURE_ANALYSIS = 'audit/failure-analysis.json'
|
||||
FULL_PLAN = 'protocol/full-trial-plan.json'
|
||||
SNAPSHOT = 'protocol/source-snapshot/'
|
||||
MAX_BYTES = 4 * 1024 ** 2
|
||||
SUFFIXES = frozenset(('.json', '.md', '.log', '.sha256'))
|
||||
SHA = re.compile(r'^[0-9a-f]{64}$')
|
||||
|
||||
|
||||
def require(ok, message):
|
||||
if not ok:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def digest(path):
|
||||
value = hashlib.sha256()
|
||||
with Path(path).open('rb') as handle:
|
||||
for block in iter(lambda: handle.read(8 << 20), b''):
|
||||
value.update(block)
|
||||
return value.hexdigest()
|
||||
|
||||
|
||||
def load_helper(path):
|
||||
raw = Path(path).read_bytes()
|
||||
require(hashlib.sha256(raw).hexdigest() == HELPER_SHA, 'Frozen seal helper SHA mismatch')
|
||||
spec = importlib.util.spec_from_file_location('phase4ao_pinned_seal_helper', path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
# Execute only the exact pinned helper; never import old experiment runners
|
||||
# or change any helper globals. No import-generated __pycache__ is written.
|
||||
exec(compile(raw, str(path), 'exec'), module.__dict__)
|
||||
return module
|
||||
|
||||
|
||||
def write_json(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 evidence exceeds the 4 MiB limit')
|
||||
with Path(path).open('xb') as handle:
|
||||
handle.write(raw)
|
||||
|
||||
|
||||
def small_json(helper, experiment, rows, name):
|
||||
require(name in rows and rows[name]['bytes'] <= MAX_BYTES, 'Missing/oversize required JSON: ' + name)
|
||||
path = helper._regular_file(experiment, name)
|
||||
require(digest(path) == rows[name]['sha256'], 'Evidence changed: ' + name)
|
||||
value = helper._json_bytes(path.read_bytes())
|
||||
require(isinstance(value, dict), 'Expected JSON object: ' + name)
|
||||
return value
|
||||
|
||||
|
||||
def source_identity(helper, root, experiment, rows, registration, registration_sha):
|
||||
model = root / MODEL_REL
|
||||
source_pins = registration.get('source_sha256')
|
||||
prepared = registration.get('prepared_file_sha256')
|
||||
require(isinstance(source_pins, dict) and source_pins and isinstance(prepared, dict),
|
||||
'Missing registered source/prepared inventory')
|
||||
files, sealed_names = [], set()
|
||||
for text, checksum in sorted(source_pins.items()):
|
||||
require(isinstance(text, str) and isinstance(checksum, str) and SHA.fullmatch(checksum),
|
||||
'Malformed registered source identity')
|
||||
path = Path(text)
|
||||
require(path.is_absolute() and path.parent == model and path.suffix in ('.py', '.md') and
|
||||
path.name not in ('', '.', '..') and '..' not in path.parts,
|
||||
'Registered source escapes the fixed model directory')
|
||||
original = helper._regular_file(root, path.relative_to(root).as_posix())
|
||||
name = SNAPSHOT + path.name
|
||||
require(name not in sealed_names and name in rows, 'Missing/duplicate registered snapshot')
|
||||
sealed_names.add(name)
|
||||
snapshot = helper._regular_file(experiment, name)
|
||||
row = rows[name]
|
||||
require(prepared.get(str(snapshot)) == checksum and row['sha256'] == checksum and
|
||||
row['bytes'] <= MAX_BYTES and original.stat().st_size == row['bytes'] and
|
||||
digest(original) == checksum and digest(snapshot) == checksum,
|
||||
'Model, registration, prepared inventory and sealed snapshot differ: ' + path.name)
|
||||
files.append(dict(model_source=path.relative_to(root).as_posix(), sealed_source=name,
|
||||
copy='source-snapshot/' + path.name, bytes=row['bytes'], sha256=checksum))
|
||||
actual = {name for name in rows if name.startswith(SNAPSHOT)}
|
||||
require(actual == sealed_names, 'Source snapshot inventory differs from registration')
|
||||
# Prepared AO files must also be the same sealed payload, not merely listed
|
||||
# in a registration whose sources happen to match.
|
||||
for text, checksum in prepared.items():
|
||||
require(isinstance(text, str) and isinstance(checksum, str) and SHA.fullmatch(checksum),
|
||||
'Malformed prepared-file identity')
|
||||
path = Path(text)
|
||||
require(path.is_absolute() and path.is_relative_to(experiment) and '..' not in path.parts,
|
||||
'Prepared file escapes this experiment')
|
||||
name = path.relative_to(experiment).as_posix()
|
||||
helper._relative_name(name)
|
||||
require(name in rows and rows[name]['sha256'] == checksum, 'Prepared file differs from seal: ' + name)
|
||||
require(FULL_PLAN in rows and registration.get('full_trial_plan_file_sha256') == rows[FULL_PLAN]['sha256'] and
|
||||
prepared.get(str(experiment / FULL_PLAN)) == rows[FULL_PLAN]['sha256'],
|
||||
'Full plan identity is not bound to the registration')
|
||||
require(type(registration.get('committed_source_blobs_verified')) is int and
|
||||
registration['committed_source_blobs_verified'] == len(files) and
|
||||
isinstance(registration.get('git_code_commit'), str) and
|
||||
re.fullmatch(r'[0-9a-f]{40}', registration['git_code_commit']), 'Missing registered source version')
|
||||
return dict(registration_sha256=registration_sha, source_count=len(files), files=files,
|
||||
git_code_commit=registration['git_code_commit'],
|
||||
original_snapshot_registration_exact=True,
|
||||
git_blobs_reverified_by_publisher=False, model_directory_sealed=False)
|
||||
|
||||
|
||||
def selection(row):
|
||||
name = row['path']
|
||||
parsed = PurePosixPath(name)
|
||||
if name == FULL_PLAN:
|
||||
return None, 'full_trial_plan_intentionally_not_exported'
|
||||
if 'extension-build' in parsed.parts or 'build' in parsed.parts or '__pycache__' in parsed.parts:
|
||||
return None, 'build_or_cache_artifact'
|
||||
if row['bytes'] > MAX_BYTES:
|
||||
return None, 'exceeds_4_MiB'
|
||||
if name.startswith(SNAPSHOT):
|
||||
return 'source-snapshot/' + parsed.name, None
|
||||
if parsed.suffix.lower() in SUFFIXES:
|
||||
return 'evidence/' + name, None
|
||||
return None, 'binary_or_non_selected_extension'
|
||||
|
||||
|
||||
def _check_roots(root, paths):
|
||||
for path in paths:
|
||||
current = root
|
||||
for part in path.relative_to(root).parts:
|
||||
current /= part
|
||||
require(not current.is_symlink(), 'Symlink in publication path: ' + str(current))
|
||||
|
||||
|
||||
def _registry_seal(helper, registry):
|
||||
files = [dict(path=p.relative_to(registry).as_posix(), bytes=p.stat().st_size, sha256=digest(p))
|
||||
for p in sorted(registry.rglob('*')) if p.is_file()]
|
||||
write_json(registry / 'manifest.json', dict(root=str(registry), files=files))
|
||||
raw = ''.join(row['sha256'] + ' ' + row['path'] + '\n' for row in files)
|
||||
raw += digest(registry / 'manifest.json') + ' manifest.json\n'
|
||||
require(len(raw.encode('utf-8')) <= MAX_BYTES, 'Registry checksums exceed small-file limit')
|
||||
with (registry / 'checksums.sha256').open('xb') as handle:
|
||||
handle.write(raw.encode('utf-8'))
|
||||
return helper.verify_seal(registry)
|
||||
|
||||
|
||||
def publish(registration_sha256, experiment_manifest_sha256, final_sha256, *, root=ROOT):
|
||||
"""Create only the fixed AO registry; verify integrity, not science.
|
||||
|
||||
The root override exists for isolated synthetic tests. Existing and partial
|
||||
registries are never overwritten, removed, or resumed.
|
||||
"""
|
||||
require(sys.dont_write_bytecode, 'Use Python -B')
|
||||
for value in (registration_sha256, experiment_manifest_sha256, final_sha256):
|
||||
require(isinstance(value, str) and SHA.fullmatch(value), 'Three external 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)
|
||||
experiment, registry, helper_path = (root / part for part in (EXP_REL, REGISTRY_REL, HELPER_REL))
|
||||
_check_roots(root, (experiment, root / MODEL_REL, registry, helper_path))
|
||||
require(not registry.exists(), 'Registry already exists; no overwrite or resume')
|
||||
helper = load_helper(helper_path)
|
||||
publisher_sha = digest(__file__)
|
||||
for relative, expected in ((REGISTRATION, registration_sha256), ('manifest.json', experiment_manifest_sha256),
|
||||
(FINAL, final_sha256)):
|
||||
require(digest(helper._regular_file(experiment, relative)) == expected, 'External anchor mismatch: ' + relative)
|
||||
verified = helper.verify_seal(experiment)
|
||||
rows = {row['path']: row for row in verified['files']}
|
||||
require(verified['manifest_sha256'] == experiment_manifest_sha256 and
|
||||
rows.get(REGISTRATION, {}).get('sha256') == registration_sha256 and
|
||||
rows.get(FINAL, {}).get('sha256') == final_sha256,
|
||||
'Verified seal differs from external registration/manifest/final anchors')
|
||||
registration = small_json(helper, experiment, rows, REGISTRATION)
|
||||
require(registration.get('schema') == 'phase4ao-conditioning-interface-registration-v1', 'Wrong phase registration')
|
||||
final = small_json(helper, experiment, rows, FINAL)
|
||||
status = final.get('status')
|
||||
require(isinstance(status, str) and re.fullmatch(r'(PASS|FAIL|INCOMPLETE)(-[A-Z0-9]+)*', status),
|
||||
'Unknown finalized status family')
|
||||
require(final.get('registration_sha256') == registration_sha256, 'Final status names another registration')
|
||||
for flag in ('learned_candidate', 'learning_success_claimed', 'full_conditioning_executed'):
|
||||
require(final.get(flag) is False, 'Final scope flag is not explicitly false: ' + flag)
|
||||
analysis_sha = final.get('analysis_sha256')
|
||||
require(isinstance(analysis_sha, str) and SHA.fullmatch(analysis_sha), 'Missing finalized analysis SHA')
|
||||
linked = [name for name in (ANALYSIS, FAILURE_ANALYSIS)
|
||||
if name in rows and rows[name]['sha256'] == analysis_sha]
|
||||
require(len(linked) == 1, 'Final analysis link must identify one sealed interface/failure analysis')
|
||||
analysis_path = linked[0]
|
||||
if 'analysis_path' in final:
|
||||
require(final['analysis_path'] == analysis_path, 'Explicit analysis path disagrees with SHA')
|
||||
analysis = small_json(helper, experiment, rows, analysis_path)
|
||||
require(analysis.get('status') == status and analysis.get('registration_sha256') == registration_sha256,
|
||||
'Final and analysis identities differ')
|
||||
sources = source_identity(helper, root, experiment, rows, registration, registration_sha256)
|
||||
inventory, plans = [], []
|
||||
for row in verified['files'] + helper._control_rows(experiment, verified):
|
||||
target, reason = selection(row)
|
||||
inventory.append(dict(**row, disposition='copied' if target else 'omitted', copy=target, reason=reason))
|
||||
if target:
|
||||
plans.append((row, target))
|
||||
targets = [target for _, target in plans]
|
||||
require(len(set(targets)) == len(targets), 'Publication destination collision')
|
||||
required = {REGISTRATION, FINAL, 'manifest.json', 'checksums.sha256'}
|
||||
required.add(analysis_path)
|
||||
require(all('evidence/' + name in targets for name in required), 'Mandatory evidence is missing/oversize')
|
||||
require(all(row['copy'] in targets for row in sources['files']), 'A registered source is not exportable')
|
||||
registry.parent.mkdir(parents=True, exist_ok=True)
|
||||
registry.mkdir(exist_ok=False)
|
||||
try:
|
||||
records = []
|
||||
for row, target in plans:
|
||||
source = helper._regular_file(experiment, row['path'])
|
||||
helper._copy_verified(source, registry / target, row['bytes'], row['sha256'])
|
||||
records.append(dict(source=source.relative_to(root).as_posix(), copy=target,
|
||||
bytes=row['bytes'], sha256=row['sha256']))
|
||||
after = helper.verify_seal(experiment)
|
||||
require(after == verified, 'Experiment changed during publication')
|
||||
require(source_identity(helper, root, experiment, rows, registration, registration_sha256) == sources,
|
||||
'Registered source changed during publication')
|
||||
require(digest(helper_path) == HELPER_SHA and digest(__file__) == publisher_sha, 'Publication/helper source changed')
|
||||
write_json(registry / 'seal-verification.json', {k: v for k, v in verified.items() if k != 'files'})
|
||||
write_json(registry / 'evidence-index.json', records)
|
||||
write_json(registry / 'source-identity.json', sources)
|
||||
write_json(registry / 'artifact-inventory.json', dict(
|
||||
experiment_manifest_sha256=experiment_manifest_sha256, files=inventory,
|
||||
omitted_files=sum(row['disposition'] == 'omitted' for row in inventory),
|
||||
omitted_bytes=sum(row['bytes'] for row in inventory if row['disposition'] == 'omitted'),
|
||||
inventory_covers_complete_experiment_and_controls=True, complete_bulk_backup=False))
|
||||
receipt_files = [dict(path=p.relative_to(registry).as_posix(), bytes=p.stat().st_size, sha256=digest(p))
|
||||
for p in sorted(registry.rglob('*')) if p.is_file()]
|
||||
receipt = dict(schema='phase4ao-publication-v1', publication_complete=True,
|
||||
registry=REGISTRY_REL.as_posix(), project_root=str(root), declared_audit_status=status,
|
||||
registration_sha256=registration_sha256, experiment_manifest_sha256=experiment_manifest_sha256,
|
||||
experiment_checksums_sha256=verified['checksums_sha256'], final_sha256=final_sha256,
|
||||
analysis_sha256=analysis_sha, analysis_path=analysis_path,
|
||||
helper_sha256=HELPER_SHA, publisher_sha256=publisher_sha,
|
||||
helper_globals_modified=False, source_count=sources['source_count'],
|
||||
copied_evidence_files=len(records), copied_evidence_bytes=sum(row['bytes'] for row in records),
|
||||
files=receipt_files, bulk_remains_at=EXP_REL.as_posix(), complete_bulk_backup=False,
|
||||
model_directory_sealed=False, scientific_gate_executed_by_publisher=False,
|
||||
scientific_gate_certified_by_publication=False, candidate_generated=False, learning_achieved=False,
|
||||
existing_files_modified=False, new_brain_run=False,
|
||||
scope='Complete source experiment byte seal verified; small evidence copied. Original status preserved, not reassessed.',
|
||||
receipt_self_hash='Returned externally; registry manifest separately seals this receipt.',
|
||||
registry_controls='manifest.json and checksums.sha256 are created after this receipt and independently verified.',
|
||||
limits=dict(max_selected_file_bytes=MAX_BYTES, selected_suffixes=sorted(SUFFIXES),
|
||||
full_trial_plan_always_omitted=True, registered_source_snapshot_exception=True))
|
||||
write_json(registry / 'publication-receipt.json', receipt)
|
||||
small_seal = _registry_seal(helper, registry)
|
||||
return dict(publication_complete=True, registry=str(registry), declared_audit_status=status,
|
||||
registration_sha256=registration_sha256, receipt_sha256=digest(registry / 'publication-receipt.json'),
|
||||
registry_manifest_sha256=small_seal['manifest_sha256'], registry_checksums_sha256=small_seal['checksums_sha256'],
|
||||
registry_payload_files=small_seal['payload_file_count'], source_count=sources['source_count'],
|
||||
copied_evidence_files=len(records), complete_bulk_backup=False,
|
||||
scientific_gate_executed_by_publisher=False, scientific_gate_certified_by_publication=False)
|
||||
except Exception as error:
|
||||
if not (registry / 'publication-failure.json').exists():
|
||||
write_json(registry / 'publication-failure.json', dict(publication_complete=False,
|
||||
error_type=type(error).__name__, error=str(error), partial_outputs_preserved=True,
|
||||
existing_files_modified=False))
|
||||
raise
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--registration-sha256', required=True)
|
||||
parser.add_argument('--experiment-manifest-sha256', required=True)
|
||||
parser.add_argument('--final-sha256', required=True)
|
||||
args = parser.parse_args()
|
||||
result = publish(args.registration_sha256, args.experiment_manifest_sha256, args.final_sha256)
|
||||
print(json.dumps(result, ensure_ascii=False, sort_keys=True, allow_nan=False), flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,53 @@
|
||||
"""Actual pinned loader integration in fresh CPU subprocesses, no brain."""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
class IsolationTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
server = Path('/data/digital-drosophila/models/male-cns-lif')
|
||||
cls.root = server if server.is_dir() else Path(__file__).resolve().parents[1]
|
||||
cls.model = cls.root / 'olfactory-conditioning-lab-v1'
|
||||
cls.helper = cls.root / 'wholebrain-plastic-interface-v1/overlay.py'
|
||||
assert cls.helper.is_file()
|
||||
|
||||
def run_child(self, body):
|
||||
setup = ('import sys, pathlib, types\n'
|
||||
f'sys.path.insert(0,{str(self.model)!r})\n'
|
||||
f'helper=pathlib.Path({str(self.helper)!r})\n'
|
||||
'import conditioning_epoch as E\n'
|
||||
'c=E._load_source(pathlib.Path(E.__file__).parent / "runtime_continuation.py", E.SOURCE_PINS["runtime_continuation_source"], "continuation")\n'
|
||||
'r=c.R\n')
|
||||
return subprocess.run([sys.executable, '-B', '-c', setup + body],
|
||||
env=dict(os.environ, CUDA_VISIBLE_DEVICES=''), capture_output=True, text=True, timeout=60)
|
||||
|
||||
def test_fresh_E_uses_one_verified_helper_cache(self):
|
||||
result = self.run_child('a=r._load("phase4an_sealed_am_overlay",helper,r.AM_OVERLAY_SHA)\n'
|
||||
'b=r._load("phase4an_sealed_am_overlay",helper,r.AM_OVERLAY_SHA)\n'
|
||||
'assert a is b\nassert "runtime_light" not in sys.modules\nassert "torch" not in sys.modules\n')
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
|
||||
def test_mixed_actual_loaders_reproduce_exact_failure(self):
|
||||
result = self.run_child('import runtime_light as ordinary\n'
|
||||
'ordinary._load("phase4an_sealed_am_overlay",helper,ordinary.AM_OVERLAY_SHA)\n'
|
||||
'r._load("phase4an_sealed_am_overlay",helper,r.AM_OVERLAY_SHA)\n')
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('Existing unverified module binding: phase4an_sealed_am_overlay', result.stderr)
|
||||
|
||||
def test_fake_preexisting_helper_is_still_rejected(self):
|
||||
result = self.run_child('sys.modules["phase4an_sealed_am_overlay"]=types.ModuleType("fake")\n'
|
||||
'r._load("phase4an_sealed_am_overlay",helper,r.AM_OVERLAY_SHA)\n')
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('Existing unverified module binding', result.stderr)
|
||||
|
||||
def test_wrong_source_pin_is_still_rejected(self):
|
||||
result = self.run_child('r._load("phase4an_sealed_am_overlay",helper,"0"*64)\n')
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('pin', result.stderr.lower())
|
||||
|
||||
|
||||
if __name__ == '__main__': unittest.main()
|
||||
@@ -0,0 +1,274 @@
|
||||
"""Temporary-directory, CPU-only publication mutations; no real experiment."""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import publish_phase4ao as P
|
||||
|
||||
|
||||
_KNOWN_HELPERS = (P.ROOT / P.HELPER_REL,
|
||||
Path(__file__).resolve().parents[1] / 'pn-recruitment-audit-v1/publication.py')
|
||||
HELPER = next((p for p in _KNOWN_HELPERS if p.is_file()), None)
|
||||
if HELPER is None or P.digest(HELPER) != P.HELPER_SHA:
|
||||
raise ValueError('Pinned publication helper absent or changed in server/local source layout')
|
||||
|
||||
|
||||
def put(path, value):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
data = value if isinstance(value, bytes) else (json.dumps(value, sort_keys=True) + '\n').encode()
|
||||
path.write_bytes(data)
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def read(path):
|
||||
return json.loads(path.read_text(encoding='utf-8'))
|
||||
|
||||
|
||||
def seal(exp):
|
||||
rows = [dict(path=p.relative_to(exp).as_posix(), bytes=p.stat().st_size, sha256=P.digest(p))
|
||||
for p in sorted(exp.rglob('*')) if p.is_file() and p.relative_to(exp).as_posix()
|
||||
not in ('manifest.json', 'checksums.sha256')]
|
||||
put(exp / 'manifest.json', dict(root=str(exp), files=rows))
|
||||
data = ''.join(r['sha256'] + ' ' + r['path'] + '\n' for r in rows)
|
||||
data += P.digest(exp / 'manifest.json') + ' manifest.json\n'
|
||||
put(exp / 'checksums.sha256', data.encode())
|
||||
|
||||
|
||||
class PublisherTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.root = Path(self.temp.name).resolve()
|
||||
self.exp = self.root / P.EXP_REL
|
||||
self.registry = self.root / P.REGISTRY_REL
|
||||
self.model = self.root / P.MODEL_REL
|
||||
put(self.root / P.HELPER_REL, HELPER.read_bytes())
|
||||
|
||||
def fixture(self, status='PASS-EXACT-TWO-EPOCH-DELTA-INTERFACE-NOT-LEARNING', count=3):
|
||||
sources, prepared = {}, {}
|
||||
names = ['worker.py', 'test_worker.py', 'PROTOCOL.md'] + [f'source_{i:03d}.py' for i in range(max(0, count - 3))]
|
||||
for name in names[:count]:
|
||||
raw = ('# synthetic registered source ' + name + '\n').encode()
|
||||
original = self.model / name
|
||||
snapshot = self.exp / P.SNAPSHOT / name
|
||||
sha = put(original, raw)
|
||||
put(snapshot, raw)
|
||||
sources[str(original)] = sha
|
||||
prepared[str(snapshot)] = sha
|
||||
full_plan = put(self.exp / P.FULL_PLAN, dict(draft_only=True, no_brain_inputs=True))
|
||||
prepared[str(self.exp / P.FULL_PLAN)] = full_plan
|
||||
self.registration = dict(schema='phase4ao-conditioning-interface-registration-v1',
|
||||
source_sha256=sources, prepared_file_sha256=prepared,
|
||||
full_trial_plan_file_sha256=full_plan, git_code_commit='4' * 40,
|
||||
committed_source_blobs_verified=len(sources))
|
||||
self.reg_sha = put(self.exp / P.REGISTRATION, self.registration)
|
||||
analysis_path = P.FAILURE_ANALYSIS if status.startswith('FAIL') else P.ANALYSIS
|
||||
analysis_sha = put(self.exp / analysis_path, dict(status=status, registration_sha256=self.reg_sha,
|
||||
complete_conditioning_executed=False, completed_rows=[] if status.startswith('FAIL') else ['synthetic']))
|
||||
self.final = dict(status=status, registration_sha256=self.reg_sha, analysis_sha256=analysis_sha,
|
||||
learned_candidate=False, learning_success_claimed=False, full_conditioning_executed=False)
|
||||
put(self.exp / P.FINAL, self.final)
|
||||
put(self.exp / 'logs/worker.log', b'partial failures are retained\n')
|
||||
put(self.exp / 'runs/first/summary.json', dict(complete=True))
|
||||
put(self.exp / 'runs/first/state.npz', b'fake binary')
|
||||
put(self.exp / 'runs/first/hashes.jsonl', b'{}\n')
|
||||
put(self.exp / 'extension-build/compiler.log', b'build output not exported')
|
||||
put(self.exp / 'reference.npy', b'not a text file')
|
||||
put(self.exp / 'large.json', b' ' * (P.MAX_BYTES + 1))
|
||||
self.reseal()
|
||||
|
||||
def reseal(self):
|
||||
seal(self.exp)
|
||||
self.anchors = (P.digest(self.exp / P.REGISTRATION), P.digest(self.exp / 'manifest.json'),
|
||||
P.digest(self.exp / P.FINAL))
|
||||
|
||||
def publish(self, *anchors):
|
||||
return P.publish(*(anchors or self.anchors), root=self.root)
|
||||
|
||||
def test_success_complete_small_seal_inventory_and_51_dynamic_sources(self):
|
||||
self.fixture(count=51)
|
||||
original = {p: P.digest(p) for p in self.exp.rglob('*') if p.is_file()}
|
||||
result = self.publish()
|
||||
self.assertTrue(result['publication_complete'])
|
||||
self.assertEqual(result['source_count'], 51)
|
||||
self.assertFalse(result['complete_bulk_backup'])
|
||||
helper = P.load_helper(self.root / P.HELPER_REL)
|
||||
verified = helper.verify_seal(self.registry)
|
||||
self.assertEqual(verified['manifest_sha256'], result['registry_manifest_sha256'])
|
||||
receipt = read(self.registry / 'publication-receipt.json')
|
||||
self.assertEqual(result['receipt_sha256'], P.digest(self.registry / 'publication-receipt.json'))
|
||||
self.assertEqual(receipt['registration_sha256'], self.anchors[0])
|
||||
self.assertFalse(receipt['scientific_gate_certified_by_publication'])
|
||||
for entry in receipt['files']:
|
||||
self.assertEqual(P.digest(self.registry / entry['path']), entry['sha256'])
|
||||
inventory = read(self.registry / 'artifact-inventory.json')['files']
|
||||
rows = {r['path']: r for r in inventory}
|
||||
self.assertEqual(set(rows), {p.relative_to(self.exp).as_posix() for p in original})
|
||||
self.assertEqual(rows[P.FULL_PLAN]['reason'], 'full_trial_plan_intentionally_not_exported')
|
||||
self.assertEqual(rows['large.json']['reason'], 'exceeds_4_MiB')
|
||||
for name in ('runs/first/state.npz', 'reference.npy', 'runs/first/hashes.jsonl', 'extension-build/compiler.log'):
|
||||
self.assertEqual(rows[name]['disposition'], 'omitted')
|
||||
self.assertEqual(rows[name]['sha256'], P.digest(self.exp / name))
|
||||
self.assertEqual((self.registry / 'evidence/manifest.json').read_bytes(), (self.exp / 'manifest.json').read_bytes())
|
||||
self.assertEqual((self.registry / 'evidence/checksums.sha256').read_bytes(), (self.exp / 'checksums.sha256').read_bytes())
|
||||
self.assertTrue(all(p.stat().st_size <= P.MAX_BYTES for p in self.registry.rglob('*') if p.is_file()))
|
||||
self.assertEqual(original, {p: P.digest(p) for p in self.exp.rglob('*') if p.is_file()})
|
||||
|
||||
def test_fail_with_partial_run_no_execution_complete_is_preserved(self):
|
||||
self.fixture('FAIL-POSTPROCESS-MODULE-CACHE')
|
||||
result = self.publish()
|
||||
self.assertEqual(result['declared_audit_status'], 'FAIL-POSTPROCESS-MODULE-CACHE')
|
||||
receipt = read(self.registry / 'publication-receipt.json')
|
||||
self.assertEqual(receipt['analysis_path'], P.FAILURE_ANALYSIS)
|
||||
self.assertFalse((self.registry / 'evidence/audit/execution-complete.json').exists())
|
||||
self.assertFalse(receipt['learning_achieved'])
|
||||
|
||||
def test_incomplete_status_is_not_upgraded(self):
|
||||
self.fixture('INCOMPLETE-NO-NONZERO-UPDATE-COVERAGE')
|
||||
self.assertEqual(self.publish()['declared_audit_status'], 'INCOMPLETE-NO-NONZERO-UPDATE-COVERAGE')
|
||||
|
||||
def test_three_external_anchors_required_and_checked(self):
|
||||
self.fixture()
|
||||
for index in range(3):
|
||||
values = list(self.anchors)
|
||||
values[index] = '0' * 64
|
||||
with self.subTest(index=index), self.assertRaisesRegex(ValueError, 'External anchor mismatch'):
|
||||
self.publish(*values)
|
||||
with self.assertRaises(ValueError):
|
||||
self.publish('', self.anchors[1], self.anchors[2])
|
||||
self.assertFalse(self.registry.exists())
|
||||
|
||||
def test_existing_registry_is_never_touched(self):
|
||||
self.fixture()
|
||||
marker = self.registry / 'do-not-touch.txt'
|
||||
put(marker, b'keep')
|
||||
with self.assertRaisesRegex(ValueError, 'already exists'):
|
||||
self.publish()
|
||||
self.assertEqual(marker.read_bytes(), b'keep')
|
||||
self.assertEqual(len(list(self.registry.iterdir())), 1)
|
||||
|
||||
def test_original_source_mutation_rejected_even_with_valid_experiment_seal(self):
|
||||
self.fixture()
|
||||
put(self.model / 'worker.py', b'# other source')
|
||||
with self.assertRaisesRegex(ValueError, 'differ'):
|
||||
self.publish()
|
||||
self.assertFalse(self.registry.exists())
|
||||
|
||||
def test_snapshot_mutation_resealed_still_rejected_by_registration(self):
|
||||
self.fixture()
|
||||
put(self.exp / P.SNAPSHOT / 'worker.py', b'# changed')
|
||||
self.reseal()
|
||||
with self.assertRaisesRegex(ValueError, 'differ'):
|
||||
self.publish()
|
||||
|
||||
def test_extra_snapshot_is_not_silently_exported(self):
|
||||
self.fixture()
|
||||
put(self.exp / P.SNAPSHOT / 'unregistered.py', b'# not registered')
|
||||
self.reseal()
|
||||
with self.assertRaisesRegex(ValueError, 'snapshot inventory'):
|
||||
self.publish()
|
||||
|
||||
def test_source_escape_rejected_after_self_consistent_resealing(self):
|
||||
self.fixture()
|
||||
old = str(self.model / 'worker.py')
|
||||
checksum = self.registration['source_sha256'].pop(old)
|
||||
external = self.root / 'unrelated/worker.py'
|
||||
put(external, (self.model / 'worker.py').read_bytes())
|
||||
self.registration['source_sha256'][str(external)] = checksum
|
||||
self.reg_sha = put(self.exp / P.REGISTRATION, self.registration)
|
||||
analysis = read(self.exp / P.ANALYSIS)
|
||||
analysis['registration_sha256'] = self.reg_sha
|
||||
self.final['analysis_sha256'] = put(self.exp / P.ANALYSIS, analysis)
|
||||
self.final['registration_sha256'] = self.reg_sha
|
||||
put(self.exp / P.FINAL, self.final)
|
||||
self.reseal()
|
||||
with self.assertRaisesRegex(ValueError, 'escapes the fixed model'):
|
||||
self.publish()
|
||||
|
||||
def test_unlisted_file_or_bad_checksum_control_rejected(self):
|
||||
self.fixture()
|
||||
put(self.exp / 'unlisted.txt', b'not sealed')
|
||||
with self.assertRaisesRegex(ValueError, 'unlisted'):
|
||||
self.publish()
|
||||
(self.exp / 'unlisted.txt').unlink()
|
||||
with (self.exp / 'checksums.sha256').open('ab') as handle:
|
||||
handle.write(b'bad line\n')
|
||||
with self.assertRaisesRegex(ValueError, 'Malformed checksums'):
|
||||
self.publish()
|
||||
|
||||
def test_final_analysis_link_and_status_must_agree(self):
|
||||
self.fixture()
|
||||
self.final['analysis_sha256'] = '0' * 64
|
||||
put(self.exp / P.FINAL, self.final)
|
||||
self.reseal()
|
||||
with self.assertRaisesRegex(ValueError, 'analysis link'):
|
||||
self.publish()
|
||||
self.final['analysis_sha256'] = P.digest(self.exp / P.ANALYSIS)
|
||||
self.final['status'] = 'FAIL-OTHER'
|
||||
put(self.exp / P.FINAL, self.final)
|
||||
self.reseal()
|
||||
with self.assertRaisesRegex(ValueError, 'identities differ'):
|
||||
self.publish()
|
||||
|
||||
def test_no_learning_claim_and_helper_pin_cannot_be_bypassed(self):
|
||||
self.fixture()
|
||||
self.final['learning_success_claimed'] = True
|
||||
put(self.exp / P.FINAL, self.final)
|
||||
self.reseal()
|
||||
with self.assertRaisesRegex(ValueError, 'scope flag'):
|
||||
self.publish()
|
||||
put(self.root / P.HELPER_REL, b'# alternate helper')
|
||||
with self.assertRaisesRegex(ValueError, 'helper SHA mismatch'):
|
||||
self.publish()
|
||||
|
||||
def test_postcopy_full_seal_detects_change_in_omitted_bulk_and_preserves_partial(self):
|
||||
self.fixture()
|
||||
helper = P.load_helper(self.root / P.HELPER_REL)
|
||||
original_copy = helper._copy_verified
|
||||
changed = False
|
||||
def interrupted_copy(*args):
|
||||
nonlocal changed
|
||||
original_copy(*args)
|
||||
if not changed:
|
||||
changed = True
|
||||
put(self.exp / 'reference.npy', b'changed omitted bytes')
|
||||
# Test-local injected failure only; production never changes helper globals.
|
||||
with mock.patch.object(helper, '_copy_verified', side_effect=interrupted_copy), mock.patch.object(P, 'load_helper', return_value=helper):
|
||||
with self.assertRaisesRegex(ValueError, 'Payload byte count|Payload SHA'):
|
||||
self.publish()
|
||||
self.assertTrue(self.registry.exists())
|
||||
self.assertTrue(read(self.registry / 'publication-failure.json')['partial_outputs_preserved'])
|
||||
self.assertFalse((self.registry / 'publication-receipt.json').exists())
|
||||
with self.assertRaisesRegex(ValueError, 'already exists'):
|
||||
self.publish()
|
||||
|
||||
def test_small_registry_seal_catches_copied_payload_mutation(self):
|
||||
self.fixture()
|
||||
self.publish()
|
||||
path = self.registry / 'evidence/logs/worker.log'
|
||||
put(path, b'tampered after publication')
|
||||
helper = P.load_helper(self.root / P.HELPER_REL)
|
||||
with self.assertRaisesRegex(ValueError, 'Payload byte count|Payload SHA'):
|
||||
helper.verify_seal(self.registry)
|
||||
|
||||
def test_valid_reseal_between_anchor_read_and_verification_is_rejected(self):
|
||||
self.fixture()
|
||||
helper = P.load_helper(self.root / P.HELPER_REL)
|
||||
original_verify = helper.verify_seal
|
||||
def resealed_verify(directory):
|
||||
put(self.exp / 'logs/worker.log', b'changed after external anchor read')
|
||||
seal(self.exp)
|
||||
return original_verify(directory)
|
||||
with mock.patch.object(helper, 'verify_seal', side_effect=resealed_verify), mock.patch.object(P, 'load_helper', return_value=helper):
|
||||
with self.assertRaisesRegex(ValueError, 'differs from external'):
|
||||
self.publish()
|
||||
self.assertFalse(self.registry.exists())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user