372 lines
24 KiB
Python
372 lines
24 KiB
Python
"""Pure temporary-file and mock-process tests; no sampler, Torch, brain or GPU."""
|
|
import copy
|
|
import importlib.util
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
import time
|
|
from types import SimpleNamespace
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
import numpy as np
|
|
import normal_runner as R
|
|
|
|
|
|
def load_readonly(name, path):
|
|
spec=importlib.util.spec_from_file_location(name,path)
|
|
module=importlib.util.module_from_spec(spec)
|
|
exec(compile(path.read_bytes(),str(path),'exec'),module.__dict__)
|
|
return module
|
|
|
|
|
|
LAB=Path(__file__).resolve().parent.parent/'olfactory-conditioning-lab-v1'
|
|
COMPOSER=load_readonly('_runner_test_original_composer',LAB/'conditioning_inputs.py')
|
|
WORKER=load_readonly('_runner_test_original_worker',LAB.parent/'conditioning-interface-v2/isolated_workers.py')
|
|
|
|
|
|
def task(arm='paired', *, requested=True, kind='train', dan='PAM15', number=1):
|
|
value=dict(task_id=f'block/normal/{arm}/epoch-{number:03d}',kind=kind,steps=1200,
|
|
block_id='block',arm=arm,mode='physiological',dan_type=dan,
|
|
compartment_verbatim="y5B'2a" if dan=='PAM15' else "y2a'1",
|
|
delta_dependency=dict(kind='zero',source=None),feedback_to_training_or_idle=kind=='train')
|
|
if kind=='probe':
|
|
value['recipe']=dict(US_requested=False,D_command=0,weight_updates_enabled=False)
|
|
else:
|
|
value['recipe']=dict(us=dict(nominal_slot_tick=900,rng_seed=None,rng_draws=0,
|
|
requested=requested,tick=900 if requested else None),
|
|
D_command=1. if requested and arm!='d_zero' else 0.,update_enabled=arm!='frozen')
|
|
return value
|
|
|
|
|
|
class RunnerTests(unittest.TestCase):
|
|
def setUp(self):
|
|
temporary=tempfile.TemporaryDirectory();self.addCleanup(temporary.cleanup)
|
|
self.root=Path(temporary.name).resolve()
|
|
for rel in ('protocol','jobs','cases','runs','updates','validation','results','receipts','logs','audit'):
|
|
(self.root/rel).mkdir()
|
|
self.addCleanup(patch.stopall)
|
|
patch.object(R,'EXP',self.root).start()
|
|
self.regfile=self.root/'protocol/preregistered.json';R.save(self.regfile,{'synthetic':True})
|
|
self.sha=R.digest(self.regfile)
|
|
self.w=R.Workflow.__new__(R.Workflow);self.w.sha=self.sha
|
|
self.w.start=time.perf_counter();self.w.completed=set();self.w.expected=[];self.w.tasks={}
|
|
self.w.overlay_dependency_pins={};self.w.case_dependency_pins={}
|
|
self.w.reg=dict(budget=dict(output_cap_bytes=8<<20,minimum_free_reserve_bytes=0,
|
|
workflow_timeout_seconds=60,child_timeout_seconds=10,not_after_utc='2099-01-01T00:00:00+00:00',
|
|
next_child_headroom_bytes=65536),runtime_input_sha256={},
|
|
input_sha256={},initial_overlay=dict(overlay_file=str(self.root/'protocol/zero.npz'),
|
|
overlay_manifest=str(self.root/'protocol/zero.json')))
|
|
R.save_npz(self.root/'protocol/zero.npz',dict(plastic_delta_mv=np.zeros(2,np.float32)))
|
|
R.save(self.root/'protocol/zero.json',dict(synthetic=True))
|
|
initial={p.relative_to(self.root).as_posix():R.identity(p) for p in (self.root/'protocol').iterdir()}
|
|
self.w.ledger=R.Ledger(self.root,self.sha,initial,8<<20,0)
|
|
|
|
def test_original_composer_no_US_D0_frozen_and_PPL_requests(self):
|
|
cs=dict(tick=np.array([10,20],np.int32),neuron_idx=np.array([3,4],np.int32))
|
|
for arm,requested,dan in (('paired',False,'PAM15'),('d_zero',True,'PAM15'),
|
|
('frozen',True,'PAM15'),('paired',True,'PPL103')):
|
|
item=task(arm,requested=requested,dan=dan)
|
|
ids=np.array([10,11,12,13] if dan=='PAM15' else [14,15],np.int32)
|
|
composed,us=R.compose_requests(cs,ids,item,COMPOSER)
|
|
self.assertEqual(len(us['tick']),len(ids) if requested else 0)
|
|
self.assertEqual(composed['audit']['US_attempts'],len(us['tick']))
|
|
self.assertEqual(item['recipe']['D_command'],0. if not requested or arm=='d_zero' else 1.)
|
|
self.assertEqual(item['recipe']['update_enabled'],arm!='frozen')
|
|
if requested:self.assertTrue(np.array_equal(us['neuron_idx'],ids))
|
|
bad=task('d_zero');bad['recipe']['D_command']=1.
|
|
with self.assertRaisesRegex(ValueError,'D/update'):R.compose_requests(cs,ids,bad,COMPOSER)
|
|
|
|
def test_probe_refuses_US_update_or_feedback(self):
|
|
cs=dict(tick=np.array([7],np.int32),neuron_idx=np.array([3],np.int32))
|
|
value=task(kind='probe');combined,us=R.compose_requests(cs,np.array([9,10],np.int32),value,COMPOSER)
|
|
self.assertEqual(len(us['tick']),0);self.assertEqual(combined['audit']['US_attempts'],0)
|
|
for role in ('US_requested','weight_updates_enabled','feedback_to_training_or_idle'):
|
|
bad=copy.deepcopy(value)
|
|
(bad if role=='feedback_to_training_or_idle' else bad['recipe'])[role]=True
|
|
with self.assertRaisesRegex(ValueError,'Probe command'):R.compose_requests(cs,np.array([9,10],np.int32),bad,COMPOSER)
|
|
|
|
def test_frozen_clock_and_reset_seed_retained(self):
|
|
original=dict(kind='fullgraph',dt_ms=.1,apl_delay_steps=18,precision='float64',
|
|
apl_precision='float32',record_history=False,seed=1234,training=False,cs_input_seed=999)
|
|
actual=R.derive_spec(original,task(),budget_root=self.root,output_cap=123456)
|
|
self.assertEqual(actual['seed'],1234);self.assertEqual(actual['steps'],1200)
|
|
self.assertEqual(actual['duration_ms'],120.);self.assertNotIn('cs_input_seed',actual)
|
|
self.assertFalse(actual['within_trial_weight_updates']);self.assertFalse(original['training'])
|
|
with self.assertRaisesRegex(ValueError,'fixed D18'):R.derive_spec(dict(original,apl_delay_steps=0),task(),budget_root=self.root,output_cap=1)
|
|
|
|
def test_actual_successful_parent_receipt_commits_ledger(self):
|
|
output=self.root/'runs/synthetic.txt'
|
|
job=dict(kind='cpu_verify',input_sha256={str(self.regfile):self.sha})
|
|
def fake_run(argv,**kwargs):
|
|
self.assertEqual(kwargs['env']['CUDA_VISIBLE_DEVICES'],'')
|
|
self.assertEqual(kwargs['env']['PYTHONDONTWRITEBYTECODE'],'1')
|
|
kwargs['stdout'].write(b'synthetic child only\n');output.write_bytes(b'new immutable output')
|
|
return SimpleNamespace(returncode=0)
|
|
with patch.object(R.subprocess,'run',side_effect=fake_run) as run:
|
|
self.w.launch('unit',job,['not-a-real-executable'],[output])
|
|
self.assertEqual(run.call_count,1);self.assertIn('unit',self.w.completed)
|
|
self.assertTrue(self.w.ledger.final_verify(['unit'])['pass_all'])
|
|
process=R.read(self.root/'receipts/unit/process.json')
|
|
self.assertEqual(process['exit_code'],0);self.assertTrue(process['complete'])
|
|
with self.assertRaisesRegex(ValueError,'rerun committed'):
|
|
self.w.launch('unit',job,['not-real'],[output])
|
|
|
|
def test_failed_child_is_uncommitted_unknown_and_cannot_rerun(self):
|
|
job=dict(kind='cpu_verify',input_sha256={str(self.regfile):self.sha})
|
|
with patch.object(R.subprocess,'run',return_value=SimpleNamespace(returncode=9)):
|
|
with self.assertRaisesRegex(ValueError,'Child failed'):self.w.launch('failed',job,['not-real'],[])
|
|
receipt=R.read(self.root/'receipts/failed/process.json')
|
|
self.assertFalse(receipt['complete']);self.assertEqual(receipt['exit_code'],9)
|
|
self.assertEqual(self.w.ledger.inspect()['uncommitted_entry_ids'],['failed'])
|
|
with self.assertRaises(FileExistsError):self.w.launch('failed',job,['not-real'],[])
|
|
|
|
def test_timed_out_child_never_gets_success_commit(self):
|
|
job=dict(kind='cpu_verify',input_sha256={str(self.regfile):self.sha})
|
|
with patch.object(R.subprocess,'run',side_effect=subprocess.TimeoutExpired('not-real',10)):
|
|
with self.assertRaisesRegex(ValueError,'Child failed'):self.w.launch('timeout',job,['not-real'],[])
|
|
receipt=R.read(self.root/'receipts/timeout/process.json')
|
|
self.assertTrue(receipt['timed_out']);self.assertIsNone(receipt['exit_code'])
|
|
self.assertNotIn('timeout',self.w.completed)
|
|
|
|
def test_spawn_OSerror_retains_failed_receipt_without_fake_exit(self):
|
|
job=dict(kind='cpu_verify',input_sha256={str(self.regfile):self.sha})
|
|
with patch.object(R.subprocess,'run',side_effect=OSError('synthetic spawn failure')):
|
|
with self.assertRaisesRegex(ValueError,'Child failed'):self.w.launch('spawn-error',job,['not-real'],[])
|
|
receipt=R.read(self.root/'receipts/spawn-error/process.json')
|
|
self.assertFalse(receipt['complete']);self.assertIsNone(receipt['exit_code'])
|
|
self.assertFalse(receipt['exit_observed_by_parent']);self.assertIn('synthetic spawn failure',receipt['launch_error'])
|
|
self.assertEqual(self.w.ledger.inspect()['uncommitted_entry_ids'],['spawn-error'])
|
|
|
|
def test_after_process_input_mutation_stops_before_commit(self):
|
|
dependency=self.root/'protocol/input.json';R.save(dependency,dict(value=1))
|
|
job=dict(kind='cpu_verify',input_sha256={str(dependency):R.digest(dependency)})
|
|
def fake_run(*a,**k):
|
|
dependency.write_bytes(b'{"value":2}');return SimpleNamespace(returncode=0)
|
|
with patch.object(R.subprocess,'run',side_effect=fake_run):
|
|
with self.assertRaisesRegex(ValueError,'Pinned input changed'):self.w.launch('mutation',job,['not-real'],[])
|
|
self.assertNotIn('mutation',self.w.completed)
|
|
|
|
def _delta_fixture(self):
|
|
old=task();new=task(number=2);new['delta_dependency']=dict(kind='epoch_delta_after',source=old['task_id']+'/delta-after')
|
|
self.w.tasks={old['task_id']:old};key=R.task_key(old)
|
|
self.w.completed.add(key+'--verify')
|
|
output=self.root/'updates'/key;output.mkdir()
|
|
R.save_npz(output/'delta-after.npz',dict(plastic_delta_mv=np.array([-.25,0.],np.float32)))
|
|
R.save(output/'overlay-manifest.json',dict(synthetic=True))
|
|
validation=self.root/'validation/previous.json';R.save(validation,dict(complete=True,
|
|
after_overlay_sha256=R.digest(output/'delta-after.npz'),after_manifest_sha256=R.digest(output/'overlay-manifest.json')))
|
|
process=self.root/f'receipts/{key}--verify/process.json';process.parent.mkdir()
|
|
R.save(process,dict(complete=True,exit_code=0,
|
|
entry_id=key+'--verify',registration_sha256=self.sha))
|
|
overlay=dict(overlay_file=str(output/'delta-after.npz'),overlay_manifest=str(output/'overlay-manifest.json'))
|
|
result=self.root/f'results/{key}.json'
|
|
R.save(result,dict(complete=True,task=old,cpu_validation=R.pin(validation),after_overlay=overlay,
|
|
cpu_process_receipt_path=str(process),update_dir=str(output)))
|
|
for path in (result,validation,process,output/'delta-after.npz',output/'overlay-manifest.json'):
|
|
self.w.ledger._tracked[path.relative_to(self.root).as_posix()]=R.identity(path)
|
|
return old,new,overlay,result,validation
|
|
|
|
def test_delta_dependency_requires_same_branch_completed_verify_and_receipt(self):
|
|
old,new,overlay,result,validation=self._delta_fixture()
|
|
self.w.completed.clear()
|
|
with self.assertRaisesRegex(ValueError,'CPU validation missing'):self.w.overlay(new)
|
|
self.w.completed.add(R.task_key(old)+'--verify')
|
|
self.assertEqual(self.w.overlay(new),overlay)
|
|
self.assertIn(str(result),self.w.overlay_dependency_pins)
|
|
self.assertIn(str(validation),self.w.overlay_dependency_pins)
|
|
for field in ('arm','block_id'):
|
|
altered=copy.deepcopy(new);altered[field]='unrelated'
|
|
with self.assertRaisesRegex(ValueError,'unrelated branch'):self.w.overlay(altered)
|
|
validation.write_bytes(b'{}')
|
|
with self.assertRaisesRegex(ValueError,'committed bytes'):self.w.overlay(new)
|
|
|
|
def test_alternate_valid_overlay_cannot_replace_previous_verified_delta(self):
|
|
_,new,_,result,_=self._delta_fixture()
|
|
alternative=self.root/'updates/other-valid';alternative.mkdir()
|
|
R.save_npz(alternative/'delta-after.npz',dict(plastic_delta_mv=np.array([-.5,0.],np.float32)))
|
|
R.save(alternative/'overlay-manifest.json',dict(synthetic=True,other=True))
|
|
for path in alternative.iterdir():self.w.ledger._tracked[path.relative_to(self.root).as_posix()]=R.identity(path)
|
|
record=R.read(result);record['update_dir']=str(alternative)
|
|
record['after_overlay']={k:str(alternative/name) for k,name in
|
|
(('overlay_file','delta-after.npz'),('overlay_manifest','overlay-manifest.json'))}
|
|
result.write_text(R.canonical(record),encoding='utf-8')
|
|
with self.assertRaisesRegex(ValueError,'committed bytes'):self.w.overlay(new)
|
|
# Even a synthetic "committed" bad result must fail its independent CPU output binding.
|
|
self.w.ledger._tracked[result.relative_to(self.root).as_posix()]=R.identity(result)
|
|
with self.assertRaisesRegex(ValueError,'independently verified output'):self.w.overlay(new)
|
|
|
|
def test_zero_dependency_does_not_carry_previous_lineage_pins(self):
|
|
self.w.overlay_dependency_pins={'stale':'0'*64}
|
|
self.assertEqual(self.w.overlay(task()),self.w.reg['initial_overlay'])
|
|
self.assertEqual(self.w.overlay_dependency_pins,{})
|
|
|
|
def _case_fixture(self):
|
|
item=task();template=self.root/'protocol/case-template.json'
|
|
R.save(template,dict(kind='fullgraph',dt_ms=.1,apl_delay_steps=18,precision='float64',
|
|
apl_precision='float32',record_history=False,seed=1234))
|
|
population=self.root/'protocol/populations.npz'
|
|
self.w.populations=dict(PAM15=np.array([10,11,12,13],np.int32),PPL103=np.array([14,15],np.int32))
|
|
R.save_npz(population,self.w.populations);self.w.reg['population_file']=str(population)
|
|
self.w.reg['input_sha256'][str(population)]=R.digest(population)
|
|
directory=self.root/'inputs/generated';directory.mkdir(parents=True)
|
|
source=directory/'source.npz';audit=directory/'source.json'
|
|
R.save_npz(source,dict(tick=np.array([5],np.int32),neuron_idx=np.array([3],np.int32)))
|
|
descriptor=dict(synthetic=True)
|
|
R.save(audit,dict(descriptor=descriptor,requests_sha256=R.digest(source)))
|
|
self.w.input_plan=dict(task_inputs={item['task_id']:'source'},descriptors={'source':descriptor})
|
|
self.w.inputs=COMPOSER
|
|
for path in (template,population,source,audit):
|
|
self.w.ledger._tracked[path.relative_to(self.root).as_posix()]=R.identity(path)
|
|
return item,template,source,audit
|
|
|
|
def test_prepare_case_reads_only_committed_template_and_shared_CS(self):
|
|
item,template,source,audit=self._case_fixture()
|
|
directory=self.w.prepare_case(item)
|
|
self.assertEqual(R.read(directory/'spec.json')['seed'],1234)
|
|
self.assertTrue({str(template),str(source),str(audit)} <= set(self.w.case_dependency_pins))
|
|
self.assertEqual(R.npz(directory/'us_requests.npz')['tick'].tolist(),[900]*4)
|
|
|
|
def test_changed_case_template_is_rejected_before_new_spec_write(self):
|
|
item,template,_,_=self._case_fixture();value=R.read(template);value['seed']=9876
|
|
template.write_text(R.canonical(value),encoding='utf-8')
|
|
with self.assertRaisesRegex(ValueError,'committed bytes'):self.w.prepare_case(item)
|
|
self.assertFalse((self.root/'cases'/R.task_key(item)/'spec.json').exists())
|
|
|
|
def test_changed_CS_and_self_rehashed_audit_are_both_rejected(self):
|
|
item,_,source,audit=self._case_fixture()
|
|
with source.open('wb') as stream:np.savez_compressed(stream,tick=np.array([9],np.int32),neuron_idx=np.array([4],np.int32))
|
|
value=R.read(audit);value['requests_sha256']=R.digest(source)
|
|
audit.write_text(R.canonical(value),encoding='utf-8')
|
|
with self.assertRaisesRegex(ValueError,'committed bytes'):self.w.prepare_case(item)
|
|
self.assertFalse((self.root/'cases'/R.task_key(item)/'requests.npz').exists())
|
|
|
|
def test_exclusive_helpers_reject_linked_parent_before_writes(self):
|
|
destination=self.root/'external';destination.mkdir();link=self.root/'alias'
|
|
try:link.symlink_to(destination,target_is_directory=True)
|
|
except OSError:self.skipTest('Platform disallows temporary symlinks')
|
|
for filename,write in (('x.json',lambda p:R.save(p,{'a':1})),
|
|
('x.npz',lambda p:R.save_npz(p,{'a':np.array([1],np.int32)}))):
|
|
with self.assertRaisesRegex(ValueError,'linked path'):write(link/filename)
|
|
self.assertFalse((destination/filename).exists())
|
|
|
|
def _mock_task(self,item):
|
|
"""Exercise real orchestration/control closures, with no scientific child."""
|
|
validator=self.root/'validator.py';validator.write_bytes(b'# not executed\n')
|
|
self.w.reg['input_sha256'][str(validator)]=R.digest(validator)
|
|
patch.object(R,'AQ',self.root).start()
|
|
target=self.root/'validate_update_cpu.py';target.write_bytes(validator.read_bytes())
|
|
self.w.reg['input_sha256'][str(target)]=R.digest(target)
|
|
for name in ('conditioning_epoch.py','conditioning_preflight_compare.py','engineering_compare.py'):
|
|
self.w.reg['input_sha256'][str(LAB/name)]=R.digest(LAB/name)
|
|
R.save(self.root/'protocol/epoch-template.json',dict(schema='synthetic',files={},frozen_inputs={}))
|
|
self.w.ledger._tracked['protocol/epoch-template.json']=R.identity(self.root/'protocol/epoch-template.json')
|
|
calls=[];authorities=[]
|
|
def case(task_value):
|
|
result=self.root/'cases'/R.task_key(task_value);result.mkdir()
|
|
R.save(result/'spec.json',dict(steps=1200));R.save_npz(result/'requests.npz',dict(tick=np.empty(0,np.int32),neuron_idx=np.empty(0,np.int32)))
|
|
return result
|
|
def ap_job(name,kind,output,**kwargs):
|
|
return dict(kind=kind,output_dir=str(output),receipt_file=str(self.root/f'receipts/{name}/worker.json'),
|
|
input_sha256={},authority_file=str(kwargs.get('authority','')))
|
|
def launch(name,job,argv,outputs,*,pre_outputs=(),post=None,ap=False):
|
|
calls.append(job['kind'])
|
|
if job['kind']=='brain':
|
|
out=Path(job['output_dir']);out.mkdir();R.save(out/'checkpoint-manifest.json',dict(synthetic=True))
|
|
R.save_npz(out/'state.npz',dict(step=np.empty(0,np.int32)))
|
|
for filename in ('process.json','worker.json'):
|
|
receipt=self.root/f'receipts/{name}/{filename}';receipt.parent.mkdir(exist_ok=True)
|
|
R.save(receipt,dict(synthetic=True))
|
|
self.w.ledger._tracked[receipt.relative_to(self.root).as_posix()]=R.identity(receipt)
|
|
elif job['kind']=='update':
|
|
authorities.append(R.read(job['authority_file']))
|
|
out=Path(job['output_dir']);out.mkdir()
|
|
R.save_npz(out/'delta-after.npz',dict(plastic_delta_mv=np.zeros(2,np.float32)))
|
|
R.save(out/'overlay-manifest.json',{});R.save(out/'chain-receipt.json',{})
|
|
else:
|
|
out=Path(job['update_dir'])
|
|
R.save(job['receipt_file'],dict(complete=True,frozen_U_verify_receipt_recomputed=True,
|
|
actual_sealed_events_used=True,after_overlay_sha256=R.digest(out/'delta-after.npz'),
|
|
epoch_authority_sha256=job['authority_sha256']))
|
|
if post is not None:post()
|
|
self.w.completed.add(name)
|
|
with patch.object(self.w,'prepare_case',side_effect=case),patch.object(self.w,'ap_job',side_effect=ap_job),\
|
|
patch.object(self.w,'launch',side_effect=launch),patch.object(self.w,'brain_verify',return_value={'synthetic':True}):
|
|
self.w.task(item)
|
|
return calls,authorities,R.read(self.root/f'results/{R.task_key(item)}.json')
|
|
|
|
def test_probe_orchestration_never_calls_E_or_CPU_update(self):
|
|
calls,authorities,record=self._mock_task(task(kind='probe'))
|
|
self.assertEqual(calls,['brain']);self.assertEqual(authorities,[])
|
|
self.assertNotIn('after_overlay',record);self.assertNotIn('cpu_validation',record)
|
|
|
|
def test_training_no_US_preserves_None_and_zero_D(self):
|
|
calls,authorities,record=self._mock_task(task(requested=False))
|
|
self.assertEqual(calls,['brain','update','cpu_verify'])
|
|
self.assertIsNone(authorities[0]['command']['us_tick'])
|
|
self.assertEqual(authorities[0]['command']['d_command'],0.)
|
|
self.assertFalse(authorities[0]['command']['frozen']);self.assertIn('after_overlay',record)
|
|
|
|
def test_D0_keeps_US_but_passes_zero_D_to_original_E(self):
|
|
calls,authorities,_=self._mock_task(task('d_zero'))
|
|
self.assertEqual(calls,['brain','update','cpu_verify'])
|
|
self.assertEqual(authorities[0]['command']['us_tick'],900)
|
|
self.assertEqual(authorities[0]['command']['d_command'],0.)
|
|
self.assertFalse(authorities[0]['command']['frozen'])
|
|
|
|
def test_frozen_keeps_D_request_but_explicitly_disables_update(self):
|
|
_,authorities,_=self._mock_task(task('frozen'))
|
|
self.assertEqual(authorities[0]['command']['d_command'],1.)
|
|
self.assertTrue(authorities[0]['command']['frozen'])
|
|
|
|
def test_PPL_compartment_not_replaced_by_PAM_or_sign_flip(self):
|
|
_,authorities,_=self._mock_task(task(dan='PPL103'))
|
|
self.assertEqual(authorities[0]['command']['compartment'],"y2a'1")
|
|
self.assertEqual(authorities[0]['command']['d_command'],1.)
|
|
self.assertEqual(authorities[0]['command']['trace_backend'],'torch')
|
|
|
|
def test_partial_task_cannot_silently_resume_remaining_children(self):
|
|
item=task();self.w.completed.add(R.task_key(item)+'--brain')
|
|
with patch.object(self.w,'launch') as launch:
|
|
with self.assertRaisesRegex(ValueError,'Partially completed task'):self.w.task(item)
|
|
launch.assert_not_called()
|
|
|
|
def test_real_AP_validation_accepts_brain_job_and_rejects_protected_outputs(self):
|
|
# Actual frozen parser only, using small synthetic files, no runtime load.
|
|
frozen=self.root/'frozen';frozen.mkdir();graph=frozen/'graph';graph.mkdir();apl=frozen/'apl';apl.mkdir()
|
|
for name in WORKER.GRAPH_FILES:(graph/name).write_bytes(b'synthetic graph bytes')
|
|
for name in WORKER.APL_FILES:(apl/name).write_bytes(b'synthetic APL bytes')
|
|
for name in ('mask.npy','base.npy','coeff.json'):(frozen/name).write_bytes(b'synthetic authority')
|
|
case=self.root/'cases/first';case.mkdir()
|
|
R.save(case/'spec.json',dict(graph_dir=str(graph),apl_bundle_dir=str(apl),coefficients_path=str(frozen/'coeff.json')))
|
|
R.save_npz(case/'requests.npz',dict(tick=np.empty(0,np.int32),neuron_idx=np.empty(0,np.int32)))
|
|
overlay=self.w.reg['initial_overlay'];manifest=Path(overlay['overlay_manifest'])
|
|
am=LAB.parent/'wholebrain-plastic-interface-v1/overlay.py'
|
|
manifest.write_text(R.canonical(dict(mask_path=str(frozen/'mask.npy'),mask_sha256=R.digest(frozen/'mask.npy'),
|
|
base_candidate_path=str(frozen/'base.npy'),base_candidate_sha256=R.digest(frozen/'base.npy'),
|
|
am_overlay_path=str(am),am_overlay_sha256=R.digest(am))),encoding='utf-8')
|
|
self.w.reg['runtime_input_sha256']={p:v['sha256'] for p,v in R.inventory(frozen).items()}
|
|
self.w.reg['runtime_input_sha256'][str(am)]=R.digest(am);self.w.h=WORKER
|
|
name='parser-brain';job=self.w.ap_job(name,'brain',self.root/'runs/first',case=case,overlay=overlay)
|
|
Path(job['receipt_file']).parent.mkdir()
|
|
file=self.root/'jobs/parser.json';R.save(file,job)
|
|
parsed,paths,_,_=WORKER.validate_job(file,R.digest(file),'brain')
|
|
self.assertEqual(parsed['diagnostic_mode'],'light');self.assertFalse(paths['output'].exists())
|
|
for field in ('output_dir','receipt_file','build_root'):
|
|
altered=copy.deepcopy(job);altered[field]=str(case/('forbidden-'+field))
|
|
alternative=self.root/'jobs'/(field+'.json');R.save(alternative,altered)
|
|
with self.assertRaisesRegex(ValueError,'protected input/source'):
|
|
WORKER.validate_job(alternative,R.digest(alternative),'brain')
|
|
self.assertFalse((case/'forbidden-output_dir').exists())
|
|
|
|
def test_separate_receipt_directories_allow_original_worker_lineage_protection(self):
|
|
# Real AP path validator should not interpret a previous receipt as
|
|
# authorizing writes into its parent. New receipts must be siblings.
|
|
prior=self.root/'receipts/previous';prior.mkdir()
|
|
previous=prior/'process.json';R.save(previous,dict(synthetic=True))
|
|
self.w.overlay_dependency_pins={str(previous):R.digest(previous)}
|
|
self.test_real_AP_validation_accepts_brain_job_and_rejects_protected_outputs()
|
|
|
|
|
|
if __name__=='__main__':unittest.main()
|