529 lines
36 KiB
Python
529 lines
36 KiB
Python
"""Read-only normal analysis, conditional on independent final integrity.
|
|
|
|
analyze(exp, registration_sha256, *, verified_inventory, final_ledger) consumes
|
|
an externally authenticated absolute-path -> {bytes, sha256} inventory and the
|
|
final append-ledger audit. Neither caller Boolean nor digest alone is treated
|
|
as an OS observation: actual ledger/start/job/parent/worker receipts and every
|
|
consumed file are rebound to bytes. SHA caching is invocation-local, never
|
|
mtime-based. The outer verifier still owns complete tree/historical integrity,
|
|
anatomical/source authority, concurrency exclusion and the final science claim.
|
|
No writes, sampling, subprocesses, follow-up assays, candidates or new gates.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from collections import defaultdict
|
|
import hashlib
|
|
import io
|
|
import json
|
|
from pathlib import Path
|
|
import types
|
|
|
|
import numpy as np
|
|
|
|
GATES_SHA256 = '8ae9bcb77fe14dbdda164f86ffdbfc1e2976be752766ef526777577223b4dd99'
|
|
PLAN_SHA256 = 'd1a6b840a547cb10b00df40f4d8b287867d09f355c9777e5f7a8b9489f5555ea'
|
|
ARM_NAMES = dict(paired='paired', d_zero='D=0', frozen='frozen', random_reward='random reward',
|
|
unpaired='unpaired', cs_only='CS-only', dan_only='DAN-only')
|
|
ODORS = {'A': '4-ethylphenol', 'B': '3-hexanone'}
|
|
POINTS = (0, 1, 2, 5, 10, 20, 50)
|
|
|
|
|
|
def require(ok, message):
|
|
if not ok:
|
|
raise ValueError(message)
|
|
|
|
|
|
def canonical(value):
|
|
return json.dumps(value, sort_keys=True, separators=(',', ':'), ensure_ascii=False, allow_nan=False)
|
|
|
|
|
|
def sha(value):
|
|
return hashlib.sha256(canonical(value).encode()).hexdigest()
|
|
|
|
|
|
def _module(name, path, expected):
|
|
raw = path.read_bytes()
|
|
require(hashlib.sha256(raw).hexdigest() == expected, 'Analysis dependency pin differs: ' + str(path))
|
|
module = types.ModuleType(name)
|
|
module.__file__ = str(path)
|
|
exec(compile(raw, str(path), 'exec'), module.__dict__)
|
|
return module
|
|
|
|
|
|
_HERE = Path(__file__).resolve().parent
|
|
G = _module('_normal_frozen_gates', _HERE.parent / 'conditioning-execution-v1/learning_gates.py', GATES_SHA256)
|
|
P = _module('_normal_frozen_plan', _HERE / 'normal_plan.py', PLAN_SHA256)
|
|
M = G.M
|
|
|
|
|
|
class Reader:
|
|
"""Actual SHA once per unique path per invocation; not an atomic snapshot."""
|
|
def __init__(self, inventory):
|
|
require(type(inventory) is dict and inventory, 'Externally verified checksum inventory required')
|
|
self.inventory = inventory
|
|
self.seen = {}
|
|
|
|
def path(self, path):
|
|
path = Path(path)
|
|
require(path.is_absolute() and '..' not in path.parts and not any(p.is_symlink() or
|
|
(hasattr(p, 'is_junction') and p.is_junction()) for p in (path, *path.parents)), 'Unsafe artifact path')
|
|
key = str(path)
|
|
require(key in self.inventory, 'Artifact absent from external checksum authority: ' + key)
|
|
expected = self.inventory[key]
|
|
require(type(expected) is dict and set(expected) == {'bytes', 'sha256'} and
|
|
type(expected['bytes']) is int and expected['bytes'] >= 0 and M._sha(expected['sha256']), 'Invalid checksum authority')
|
|
return path, expected
|
|
|
|
def check(self, path):
|
|
path, expected = self.path(path)
|
|
key = str(path)
|
|
if key not in self.seen:
|
|
require(path.is_file(), 'Missing artifact: ' + key)
|
|
digest = hashlib.sha256()
|
|
size = 0
|
|
with path.open('rb') as stream:
|
|
for part in iter(lambda: stream.read(8 << 20), b''):
|
|
digest.update(part); size += len(part)
|
|
require(dict(bytes=size, sha256=digest.hexdigest()) == expected, 'Actual file checksum differs: ' + key)
|
|
self.seen[key] = dict(expected)
|
|
require(self.seen[key] == expected, 'Checksum authority changed during analysis')
|
|
return expected
|
|
|
|
def raw(self, path):
|
|
self.check(path)
|
|
# Parsed data are read from the now-bound file, not retained for every
|
|
# trajectory. Concurrent mutation remains an outer quiescence obligation.
|
|
return Path(path).read_bytes()
|
|
|
|
def json(self, path):
|
|
def unique(items):
|
|
result = {}
|
|
for key, value in items:
|
|
require(key not in result, 'Duplicate JSON field'); result[key] = value
|
|
return result
|
|
return json.loads(self.raw(path).decode('utf-8'), object_pairs_hook=unique,
|
|
parse_constant=lambda _: (_ for _ in ()).throw(ValueError('Nonfinite JSON')))
|
|
|
|
def arrays(self, path):
|
|
with np.load(io.BytesIO(self.raw(path)), allow_pickle=False) as archive:
|
|
return {key: archive[key].copy() for key in archive.files}
|
|
|
|
|
|
def task_key(task):
|
|
return 'task-' + hashlib.sha256(task['task_id'].encode()).hexdigest()
|
|
|
|
|
|
def expected_entries(plan):
|
|
result = ['input-generation']
|
|
for task in plan['tasks']:
|
|
result.extend(task_key(task) + suffix for suffix in
|
|
(('--brain', '--update', '--verify') if task['kind'] == 'train' else ('--brain',)))
|
|
return result
|
|
|
|
|
|
def audit_case(reader, exp, task, job, *, template, inputs, sampler, populations, population_file, output_cap):
|
|
"""Independent raw CS/US composition and reset/spec checks; no brain call.
|
|
|
|
A scheduled US request is not an accepted spike or a successful behavior.
|
|
The artificial D scalar remains the original recipe, never an event count.
|
|
"""
|
|
exp = Path(exp); case = exp / 'cases' / task_key(task)
|
|
require(job['case_dir'] == str(case), 'Case path differs from original task')
|
|
spec = reader.json(case / 'spec.json')
|
|
removed = ('validation_cohort', 'odor_index', 'expected_external_events', 'training', 'cs_input_seed', 'cs_input_seed_bits',
|
|
'seed_scope', 'odor_id', 'odor_name', 'source_panel_row', 'source_panel_split', 'rate_sum_hz', 'target_count',
|
|
'expected_CS_requests', 'CS_window', 'outer_epoch_id', 'outer_arm', 'outer_protocol')
|
|
fixed = {k: v for k, v in template.items() if k not in removed}
|
|
fixed.update(name=task_key(task), steps=1200, duration_ms=120., max_steps=1200, budget_root=str(exp),
|
|
output_budget_bytes=output_cap, mode=task['mode'], within_trial_weight_updates=False,
|
|
outer_protocol='normal-acquisition-v1', outer_task_id=task['task_id'], outer_arm=task['arm'])
|
|
require(canonical(spec) == canonical(fixed) and spec['seed'] == template['seed'] and spec['dt_ms'] == .1 and
|
|
spec['apl_delay_steps'] == 18 and spec['precision'] == 'float64' and spec['apl_precision'] == 'float32' and
|
|
spec['record_history'] is False and spec['kind'] == 'fullgraph', 'Scientific model/reset/clock spec changed')
|
|
recipe = task['recipe']
|
|
cs_recipe = recipe['cs'] if task['kind'] == 'train' else recipe
|
|
expected = None
|
|
if cs_recipe is not None:
|
|
cue = cs_recipe['cue']
|
|
seed = cs_recipe['seed'] if task['kind'] == 'train' else cs_recipe['input']['seed']
|
|
stream = cs_recipe['source_stream_id'] if task['kind'] == 'train' else cs_recipe['input']['stream_id']
|
|
require(type(seed) is int and 0 <= seed < 2**63, 'Original full63 seed required')
|
|
expected = dict(mode=task['mode'], cue=cue, seed=seed, steps=1200, cs_start_tick=0,
|
|
cs_stop_tick_exclusive=1000, source_stream_id=stream, table_id=task['mode'] + '--' + cue)
|
|
csid = None if expected is None else sha(expected)
|
|
require(inputs['task_inputs'][task['task_id']] == csid, 'Original task/CS descriptor mapping differs')
|
|
binding = reader.json(case / 'input-binding.json')
|
|
require(binding['task_id'] == task['task_id'] and binding['CS_descriptor_id'] == csid and
|
|
binding['DAN_type'] == task['dan_type'] and binding['population_file'] ==
|
|
dict(path=population_file, sha256=reader.check(population_file)['sha256']), 'Case input/population binding differs')
|
|
empty = dict(tick=np.empty(0, np.int32), neuron_idx=np.empty(0, np.int32))
|
|
source = empty
|
|
if csid is None:
|
|
require(binding['CS_source'] is None, 'No-CS epoch acquired an odor source')
|
|
else:
|
|
npzpath = exp / f'inputs/generated/{csid}.npz'; jsonpath = exp / f'inputs/generated/{csid}.json'
|
|
generated = reader.json(jsonpath); audit = generated['audit']
|
|
require(inputs['descriptors'][csid] == generated['descriptor'] == expected and
|
|
generated['requests_sha256'] == reader.check(npzpath)['sha256'] and
|
|
binding['CS_source'] == dict(path=str(npzpath), sha256=reader.check(npzpath)['sha256']) and
|
|
sampler['inputs'][csid] == dict(requests=binding['CS_source'], audit=dict(path=str(jsonpath), sha256=reader.check(jsonpath)['sha256'])),
|
|
'Saved sampler receipt/descriptor differs')
|
|
require(audit['seed'] == audit['generator_initial_seed'] == seed and audit['seed_bits'] == 63 and
|
|
audit['seed_conversion'] == 'exact integer; no truncation/modulo/offset' and audit['private_generator'] is True and
|
|
audit['matrix_draw_calls'] == 1 and audit['uniform_matrix_shape'][0] == 1200 and audit['CS_window'] == [0, 1000],
|
|
'Original sampler seed/full-shape/window evidence differs')
|
|
source = reader.arrays(npzpath)
|
|
|
|
def keys(request):
|
|
require(set(request) == {'tick', 'neuron_idx'} and all(v.dtype == np.int32 and v.ndim == 1 for v in request.values()) and
|
|
request['tick'].shape == request['neuron_idx'].shape and np.all((request['tick'] >= 0) & (request['tick'] < 1200)) and
|
|
np.all((request['neuron_idx'] >= 0) & (request['neuron_idx'] < 166700)), 'Invalid raw request arrays')
|
|
result = request['tick'].astype(np.int64) * 166700 + request['neuron_idx']
|
|
require(np.all(result[1:] > result[:-1]), 'Requests are not sorted unique')
|
|
return result
|
|
|
|
source_keys = keys(source)
|
|
require(np.all(source['tick'] < 1000), 'CS escaped original window')
|
|
cs, us, combined = (reader.arrays(case / name) for name in ('cs_requests.npz', 'us_requests.npz', 'requests.npz'))
|
|
cs_keys, us_keys, combined_keys = keys(cs), keys(us), keys(combined)
|
|
require(np.array_equal(source_keys, cs_keys), 'Actual case CS differs from original saved input')
|
|
requested = task['kind'] == 'train' and recipe['us']['requested']
|
|
dan = populations[task['dan_type']]
|
|
require(dan.dtype == np.int32 and dan.ndim == 1 and np.all(dan[1:] > dan[:-1]), 'Actual DAN mapping differs')
|
|
require(binding['DAN_membership_sha256'] == hashlib.sha256(dan.tobytes()).hexdigest(), 'DAN membership binding differs')
|
|
expected_us = 900 * 166700 + dan.astype(np.int64) if requested else np.empty(0, np.int64)
|
|
require(np.array_equal(us_keys, expected_us) and not np.intersect1d(cs_keys, us_keys).size and
|
|
np.array_equal(combined_keys, np.union1d(cs_keys, us_keys)), 'Actual scheduled US/CS composition differs')
|
|
masks = reader.arrays(case / 'source-mask.npz')
|
|
expected_mask = np.where(np.isin(combined_keys, cs_keys), 1, 2).astype(np.uint8)
|
|
require(set(masks) == {'source_mask'} and masks['source_mask'].dtype == np.uint8 and
|
|
np.array_equal(masks['source_mask'], expected_mask) and binding['composition']['policy'] == 'error' and
|
|
binding['composition']['cross_source_conflict_count'] == 0, 'Raw composition/source-mask differs')
|
|
|
|
|
|
class Evidence:
|
|
def __init__(self, reader, exp, registration_sha, expected, final):
|
|
require(final.get('schema') == 'normal-append-ledger-v1-final-audit' and
|
|
final.get('registration_sha256') == registration_sha and final.get('pass_all') is True and
|
|
final.get('entry_ids') == final.get('expected_entry_ids') == expected and
|
|
final.get('expected_order_exact') is True, 'Final ledger authority/complete order differs')
|
|
require(all(final.get(k) == [] for k in ('extra_files', 'missing_files', 'changed_files',
|
|
'unsafe_files', 'errors', 'uncommitted_entry_ids')) and final.get('uncommitted_state_unknown') is False,
|
|
'Incomplete/failed final ledger audit')
|
|
self.r, self.exp, self.regsha, self.expected = reader, Path(exp), registration_sha, expected
|
|
self.index, self.tail, self.final = 0, None, final
|
|
|
|
def process(self, entry_id, kind):
|
|
self.index += 1
|
|
require(self.expected[self.index - 1] == entry_id, 'Analysis process order differs')
|
|
entry_path = self.exp / f'.append-ledger/entries/{self.index:016d}.json'
|
|
entry = self.r.json(entry_path)
|
|
start_path = self.exp / f'.append-ledger/started/{entry_id}.json'
|
|
start = self.r.json(start_path)
|
|
job_path = self.exp / f'jobs/{entry_id}.json'
|
|
process_path = self.exp / 'receipts' / entry_id / 'process.json'
|
|
require(entry['entry_id'] == start['entry_id'] == entry_id and entry['sequence'] == start['next_sequence'] == self.index and
|
|
entry['registration_sha256'] == start['registration_sha256'] == self.regsha and
|
|
entry['prev_entry_sha256'] == start['prev_entry_sha256'] == self.tail and
|
|
entry['start_sha256'] == self.r.check(start_path)['sha256'] and start['retry_authorized'] is False,
|
|
'Ledger/start chain differs')
|
|
incoming = {row['path']: row for row in entry['consumed_inputs']}
|
|
outgoing = {row['path']: row for row in entry['new_outputs']}
|
|
require(len(incoming) == len(entry['consumed_inputs']) and len(outgoing) == len(entry['new_outputs']), 'Duplicate ledger files')
|
|
for path, row in {**incoming, **outgoing}.items():
|
|
_, bound = self.r.path(path)
|
|
require(all(row[k] == bound[k] for k in ('bytes', 'sha256')), 'Ledger/checksum authority differs')
|
|
metadata = entry['metadata']
|
|
require(metadata['job_file'] == str(job_path) and metadata['process_receipt_file'] == str(process_path) and
|
|
type(metadata['observed_returncode']) is int and metadata['observed_returncode'] == 0 and
|
|
str(process_path) in outgoing and str(job_path) in incoming and
|
|
start['job']['path'] == str(job_path) and start['job']['sha256'] == self.r.check(job_path)['sha256'],
|
|
'Process/job not owned by committed ledger entry')
|
|
process, job = self.r.json(process_path), self.r.json(job_path)
|
|
require(process['entry_id'] == entry_id and process['registration_sha256'] == job['registration_sha256'] == self.regsha and
|
|
process['job_sha256'] == self.r.check(job_path)['sha256'] and process['complete'] is True and
|
|
type(process['exit_code']) is int and process['exit_code'] == 0 and process['timed_out'] is False and
|
|
process['exit_observed_by_parent'] is True and job['kind'] == kind, 'Actual parent exit/job receipt differs')
|
|
for path, digest in job['input_sha256'].items():
|
|
require(path in incoming and self.r.check(path)['sha256'] == digest, 'Actual consumed job input differs')
|
|
for label in ('stdout', 'stderr'):
|
|
path = self.exp / f'logs/{entry_id}.{label}.log'
|
|
require(str(path) in outgoing and self.r.check(path)['sha256'] == process[label + '_sha256'], 'Parent log differs')
|
|
if kind in ('brain', 'update'):
|
|
require(job['receipt_file'] in outgoing and job['receipt_file'] + '.attempt.json' in outgoing,
|
|
'Worker/attempt receipt not ledger owned')
|
|
worker = self.r.json(job['receipt_file'])
|
|
closure = dict(job['input_sha256'])
|
|
closure[str(job_path)] = self.r.check(job_path)['sha256']
|
|
# The AP worker includes its own exact script source in its closure.
|
|
script = process['argv'][2]
|
|
closure[script] = self.r.check(script)['sha256']
|
|
for value in job['sources'].values():
|
|
require(self.r.check(value['path'])['sha256'] == value['sha256'], 'Worker source differs')
|
|
closure[value['path']] = value['sha256']
|
|
require(worker['schema'] == 'phase4ap-isolated-worker-receipt-v1' and worker['complete'] is True and
|
|
worker['job_id'] == entry_id and worker['kind'] == kind and worker['registration_sha256'] == self.regsha and
|
|
worker['job_sha256'] == self.r.check(job_path)['sha256'] and worker['verified_input_sha256'] == closure and
|
|
worker['attempt_sha256'] == self.r.check(job['receipt_file'] + '.attempt.json')['sha256'], 'Worker/attempt binding differs')
|
|
declared = {str(Path(p).relative_to(job['output_dir'])): row['sha256'] for p, row in outgoing.items()
|
|
if Path(p).is_relative_to(job['output_dir'])}
|
|
require(worker['output_files_sha256'] == declared, 'Worker output coverage differs')
|
|
self.tail = self.r.check(entry_path)['sha256']
|
|
return dict(job=job, outgoing=outgoing, process_sha256=self.r.check(process_path)['sha256'], process_path=str(process_path))
|
|
|
|
def finish(self):
|
|
require(self.index == len(self.expected) and self.tail == self.final['head_sha256'], 'Missing process or terminal ledger head differs')
|
|
|
|
|
|
def audit_records(reader, exp, reg, regsha, plan, evidence, *, template, inputs, populations):
|
|
"""Bind all 33,920 tasks and actual update chains; retain only small indexes."""
|
|
sample = evidence.process('input-generation', 'sample')
|
|
sampler = reader.json(Path(exp) / 'receipts/input-generation.json')
|
|
require(sampler['complete'] is True and sampler['registration_sha256'] == regsha and
|
|
str(Path(exp) / 'receipts/input-generation.json') in sample['outgoing'], 'Sampler receipt not ledger owned')
|
|
require(sampler['descriptors'] == len(inputs['descriptors']) == 4080 and set(sampler['inputs']) == set(inputs['descriptors']) and
|
|
sampler['input_inventory_sha256'] == reader.check(Path(exp) / 'protocol/input-inventory.json')['sha256'], 'Actual sampler inventory incomplete')
|
|
previous, probes, arms, record_hashes = {}, {}, defaultdict(list), []
|
|
for task in plan['tasks']:
|
|
key = task_key(task); result_path = Path(exp) / f'results/{key}.json'
|
|
record = reader.json(result_path)
|
|
require(record['schema'] == 'normal-task-result-v1' and record['complete'] is True and
|
|
record['registration_sha256'] == regsha and canonical(record['task']) == canonical(task), 'Actual result/task differs')
|
|
branch = task['block_id'], task['arm']
|
|
overlay = previous.get(branch, reg['initial_overlay'])
|
|
require(record['overlay_before'] == overlay, 'Delta ancestry changed or probe fed back')
|
|
brain = evidence.process(key + '--brain', 'brain')
|
|
audit_case(reader, exp, task, brain['job'], template=template, inputs=inputs, sampler=sampler,
|
|
populations=populations, population_file=reg['population_file'], output_cap=reg['budget']['output_cap_bytes'])
|
|
job = brain['job']; output = Path(exp) / 'runs' / key
|
|
require(record['brain_process_receipt_path'] == brain['process_path'] and
|
|
record['checkpoint_dir'] == job['output_dir'] == str(output) and
|
|
all(job[k] == overlay[k] for k in ('overlay_file', 'overlay_manifest')) and
|
|
job['diagnostic_mode'] == 'light', 'Brain result/overlay path differs')
|
|
manifest = reader.json(output / 'checkpoint-manifest.json')
|
|
require(record['checkpoint_manifest_sha256'] == record['brain_audit']['checkpoint_manifest_sha256'] ==
|
|
reader.check(output / 'checkpoint-manifest.json')['sha256'] and
|
|
record['brain_audit']['task_id'] == task['task_id'], 'Checkpoint binding differs')
|
|
for name, digest in manifest['files_sha256'].items():
|
|
require(reader.check(output / name)['sha256'] == digest, 'Checkpoint payload differs')
|
|
record_hashes.append([task['task_id'], reader.check(result_path)['sha256']])
|
|
if task['kind'] == 'probe':
|
|
require(str(result_path) in brain['outgoing'], 'Probe result not ledger owned')
|
|
probes[(task['block_id'], task['arm'], task['checkpoint'], task['recipe']['cue'])] = record
|
|
continue
|
|
update = evidence.process(key + '--update', 'update')
|
|
cpu = evidence.process(key + '--verify', 'cpu_verify')
|
|
require(record['update_process_receipt_path'] == update['process_path'] and record['cpu_process_receipt_path'] == cpu['process_path'] and
|
|
str(result_path) in cpu['outgoing'], 'Training result/parent process ownership differs')
|
|
authority = reader.json(record['epoch_authority']['path'])
|
|
require(reader.check(record['epoch_authority']['path'])['sha256'] == record['epoch_authority']['sha256'] ==
|
|
update['job']['authority_sha256'] == cpu['job']['authority_sha256'] and authority['epoch_id'] == task['task_id'] and
|
|
authority['checkpoint_dir'] == str(output) and authority['checkpoint_manifest_sha256'] == record['checkpoint_manifest_sha256'],
|
|
'Original E authority differs')
|
|
command = dict(steps=1200, compartment=task['compartment_verbatim'], us_tick=task['recipe']['us']['tick'],
|
|
d_command=task['recipe']['D_command'], frozen=not task['recipe']['update_enabled'], trace_backend='torch')
|
|
require(authority['command'] == command, 'Original E command changed')
|
|
valid = reader.json(record['cpu_validation']['path'])
|
|
after = dict(overlay_file=str(Path(exp) / 'updates' / key / 'delta-after.npz'),
|
|
overlay_manifest=str(Path(exp) / 'updates' / key / 'overlay-manifest.json'))
|
|
require(record['update_dir'] == update['job']['output_dir'] == cpu['job']['update_dir'] == str(Path(exp) / 'updates' / key) and
|
|
record['cpu_validation']['path'] == cpu['job']['receipt_file'] and record['cpu_validation']['path'] in cpu['outgoing'] and
|
|
record['epoch_authority']['path'] == update['job']['authority_file'] == cpu['job']['authority_file'] and
|
|
authority['files']['state'] == dict(path=str(output / 'state.npz'), sha256=reader.check(output / 'state.npz')['sha256']) and
|
|
authority['files']['overlay_before'] == dict(path=overlay['overlay_file'], sha256=reader.check(overlay['overlay_file'])['sha256']) and
|
|
authority['files']['overlay_manifest_before'] == dict(path=overlay['overlay_manifest'], sha256=reader.check(overlay['overlay_manifest'])['sha256']) and
|
|
record['after_overlay'] == after and reader.check(record['cpu_validation']['path'])['sha256'] == record['cpu_validation']['sha256'] and
|
|
valid['complete'] is True and valid['frozen_U_verify_receipt_recomputed'] is True and valid['actual_sealed_events_used'] is True and
|
|
valid['epoch_authority_sha256'] == record['epoch_authority']['sha256'] and
|
|
valid['after_overlay_sha256'] == reader.check(after['overlay_file'])['sha256'], 'Independent CPU update validation differs')
|
|
if task['arm'] in ('d_zero', 'frozen', 'cs_only'):
|
|
require(not reader.arrays(after['overlay_file'])['plastic_delta_mv'].view(np.uint32).any(), 'Zero control contains nonzero delta')
|
|
previous[branch] = after
|
|
arms[branch].append(dict(result=record_hashes[-1], brain=brain['process_sha256'],
|
|
update=update['process_sha256'], cpu=cpu['process_sha256'], requested=task['recipe']['us']['requested']))
|
|
evidence.finish()
|
|
require(len(probes) == 3920 and len(arms) == 280 and len(record_hashes) == 33920, 'Incomplete actual task inventory')
|
|
return probes, arms, sha(record_hashes)
|
|
|
|
|
|
def build_registry(trial, plan, *, nodes, populations, population_binding_sha, regsha, input_hashes):
|
|
membership = M.validate_populations(nodes, populations)
|
|
blocks = {}
|
|
inputs = {(r['block_id'], r['cue']): r for r in plan['evaluation_inputs']}
|
|
for block in trial['plan']['blocks']:
|
|
identity = block['block_id']
|
|
source_rows = [r for r in trial['seed_manifest']['independent_streams']
|
|
if r['key'][0] == identity and r['key'][1] == 'normal' and r['split'] in ('training', 'design')]
|
|
sources = [dict(stream_id=P._FROZEN['trial_plan']._stream_id(P.MASTER, r['split'], r['key']),
|
|
seed=r['seed'], split=r['split']) for r in source_rows]
|
|
blocks[identity] = dict(mode=block['mode'], dan_type=block['dan_type'], compartment_verbatim=block['compartment_verbatim'],
|
|
conditioned_cue=ODORS[block['initial_reinforced_cue']], replicate=block['replicate'], training_sources=sources,
|
|
evaluation_sources={ODORS[c]: {k: inputs[(identity, c)][k] for k in ('stream_id', 'seed', 'split')} for c in ODORS},
|
|
evaluation_input_sha256={ODORS[c]: input_hashes[(identity, c)] for c in ODORS})
|
|
registry = dict(schema=G.REGISTRY_SCHEMA, protocol_sha256=regsha, population_binding_sha256=population_binding_sha,
|
|
nodes_raw_sha256=M._hash(nodes), population_membership_sha256=membership['membership_sha256'], blocks=blocks)
|
|
G.validate_registry(registry, expected_sha256=G.descriptor_sha256(registry))
|
|
return registry
|
|
|
|
|
|
def compact_metric(metric):
|
|
return dict(rawS=metric['rawS'], rawB=metric['rawB'], separation=metric['separation'],
|
|
cues={cue: dict(state_pre=summary['pre_state_raw_sha256'], state_post=summary['post_state_raw_sha256'],
|
|
populations={name: {k: v for k, v in group.items() if not isinstance(v, list)}
|
|
for name, group in summary['populations'].items()}) for cue, summary in metric['cues'].items()})
|
|
|
|
|
|
def scientific_reports(probes, arms, *, registry, authority, nodes, populations, load_state):
|
|
"""One cell's terminal vectors at a time; curve outputs contain no raw vectors."""
|
|
curves, compact_reports = [], []
|
|
|
|
def cells():
|
|
for mode in M.MODES:
|
|
for dan in M.DAN_TYPES:
|
|
for conditioned in M.CUES:
|
|
rows = []
|
|
selected = [(key, b) for key, b in registry['blocks'].items()
|
|
if (b['mode'], b['dan_type'], b['conditioned_cue']) == (mode, dan, conditioned)]
|
|
for identity, block in selected:
|
|
arm_values = {}
|
|
for name, legacy_name in ARM_NAMES.items():
|
|
pre = {ODORS[c]: load_state(probes[(identity, name, 0, c)]) for c in ODORS}
|
|
terminal = None
|
|
for point in POINTS:
|
|
post = pre if point == 0 else {ODORS[c]: load_state(probes[(identity, name, point, c)]) for c in ODORS}
|
|
metric = M.arm_metrics(pre, post, conditioned_cue=conditioned, nodes=nodes, populations=populations)
|
|
curves.append(dict(block_id=identity, arm=name, checkpoint=point, metrics=compact_metric(metric)))
|
|
if point == 50:
|
|
terminal = metric
|
|
history = arms[(identity, name)]
|
|
require(len(history) == (150 if name == 'unpaired' else 100), 'Missing actual training/control epochs')
|
|
execution = dict(complete=True, recording_verified=True, receipt_sha256=sha(history), assay='normal',
|
|
epoch_duration_ms=120., cs_duration_ms=100., us_tick=900, completed_epochs=len(history),
|
|
reinforcement_opportunities=sum(r['requested'] for r in history), terminal_pairings=50)
|
|
arm_values[legacy_name] = dict(metrics=terminal, execution=execution)
|
|
rows.append(dict(replicate_id=identity, replicate_unit='independent_training',
|
|
training_stream_ids=[x['stream_id'] for x in block['training_sources']],
|
|
evaluation_stream_ids=[block['evaluation_sources'][c]['stream_id'] for c in M.CUES],
|
|
assay='normal', mode=mode, dan_type=dan, conditioned_cue=conditioned, arms=arm_values))
|
|
report = G.evaluate_acquisition(rows, assay='normal', mode=mode, dan_type=dan,
|
|
conditioned_cue=conditioned, registry=registry, authority=authority)
|
|
compact_reports.append({k: v for k, v in report.items()
|
|
if k not in ('acquisition_metric_inputs', 'frozen_acquisition_result')})
|
|
yield report
|
|
|
|
cohort = G.summarize_cohort(cells(), registry=registry, authority=authority, required_assays=('normal',))
|
|
require(cohort['complete_inventory'], 'Missing normal cells')
|
|
return dict(cells=compact_reports, cohort=cohort, curves=curves)
|
|
|
|
|
|
def legacy_audit(reports, declared_mapping):
|
|
"""G.H_MAPPING source layout; call the unchanged M scalar twice per group."""
|
|
require(declared_mapping == G.H_MAPPING, 'Explicit original registered H mapping required')
|
|
lookup = {(r['mode'], r['dan_type'], r['conditioned_cue']): r for r in reports}
|
|
require(len(reports) == len(lookup) == 8, 'Exact eight-cell legacy input required')
|
|
groups = []
|
|
for mode in M.MODES:
|
|
for cue in M.CUES:
|
|
selected = {d: lookup[(mode, d, cue)] for d in M.DAN_TYPES}
|
|
outcomes = {}
|
|
for source in M.DAN_TYPES:
|
|
gains = {label: [r['population_separation_gains']['MBON'] for r in selected[dan]['replicates']]
|
|
for dan, label in (('PAM15', 'appetitive-conditioning'), ('PPL103', 'aversive-conditioning'))}
|
|
for name, label in (('D=0', 'd-zero-control'), ('random reward', 'random-reward-control'), ('frozen', 'frozen-brain-control')):
|
|
controls = selected[source]['legacy_control_MBON_gains'][name]
|
|
require(set(controls) == {r['replicate_id'] for r in selected[source]['replicates']}, 'Legacy replicate control source differs')
|
|
gains[label] = [controls[r['replicate_id']] for r in selected[source]['replicates']]
|
|
outcomes[source] = M.legacy_h_mean_control_audit(gains)
|
|
groups.append(dict(mode=mode, conditioned_cue=cue, control_sources=outcomes,
|
|
both_pass=all(v['predicate_pass'] for v in outcomes.values())))
|
|
return dict(schema=G.SCHEMA, status='CONDITIONAL-AUDIT', declared_mapping=declared_mapping, groups=groups,
|
|
both_pass=all(g['both_pass'] for g in groups), original_H_data_layout_reproduced=False,
|
|
new_stricter_control_source_mapping=True, **G._scope())
|
|
|
|
|
|
def analyze(exp, registration_sha256, *, verified_inventory, final_ledger):
|
|
exp, reader = Path(exp), Reader(verified_inventory)
|
|
regpath = exp / 'protocol/preregistered.json'
|
|
require(reader.check(regpath)['sha256'] == registration_sha256, 'Registration authority differs')
|
|
reg = reader.json(regpath)
|
|
require(reg['schema'] == 'normal-conditioning-registration-v1' and reg['scientific_scope'] == 'normal-acquisition-only' and
|
|
reg['subsequent_assays_authorized'] is False, 'Wrong scientific authority')
|
|
for collection in ('source_sha256', 'input_sha256', 'prepared_sha256'):
|
|
for path, digest in reg[collection].items():
|
|
require(reader.check(path)['sha256'] == digest, 'Registered dependency differs')
|
|
plan = reader.json(exp / 'protocol/normal-plan.json')
|
|
full = reader.json(reg['original_full_trial_plan']['path'])
|
|
require(reader.check(reg['original_full_trial_plan']['path'])['sha256'] == reg['original_full_trial_plan']['sha256'] == P.AO_TRIAL_FILE_SHA256,
|
|
'Original AO full plan differs')
|
|
evaluation = P.expand_pinned_evaluation(full)
|
|
P.validate_normal_plan(plan, full, evaluation)
|
|
gates = reader.json(exp / 'protocol/gates.json')
|
|
require(gates['H_mapping'] == G.H_MAPPING and gates['all_eight_cells_required'] is True and
|
|
gates['same_replicate_conjunction_at_least'] == 3 and gates['training_replicates'] == 5 and gates['terminal_checkpoint'] == 50 and
|
|
gates['intermediate_checkpoints_cannot_rescue_terminal'] is True, 'Registered scientific policy differs')
|
|
template = reader.json(exp / 'protocol/case-template.json')
|
|
nodes_path = Path(template['graph_dir']) / 'nodes.npy'
|
|
nodes = np.load(io.BytesIO(reader.raw(nodes_path)), allow_pickle=False)
|
|
require(nodes.dtype == np.int32 and len(nodes) == 166700, 'Full original graph nodes required')
|
|
require('readout_population_file' in reg, 'Registered full legacy/non-MBON readout populations missing')
|
|
populations = reader.arrays(reg['readout_population_file'])
|
|
original = reader.arrays(reg['population_file'])
|
|
for name in ('MBON', 'MBON01', 'MBON12'):
|
|
require(np.array_equal(original[name], populations[name]), 'Original anatomical population changed')
|
|
M.validate_populations(nodes, populations)
|
|
require('readout_population_binding_file' in reg, 'Registered anatomical binding file missing')
|
|
population_binding_sha = reader.check(reg['readout_population_binding_file'])['sha256']
|
|
evidence = Evidence(reader, exp, registration_sha256, expected_entries(plan), final_ledger)
|
|
inputs = reader.json(exp / 'protocol/input-inventory.json')
|
|
probes, arms, inventory_sha = audit_records(reader, exp, reg, registration_sha256, plan, evidence,
|
|
template=template, inputs=inputs, populations=original)
|
|
input_hashes, seen_receipts = {}, set()
|
|
|
|
def load_state(record):
|
|
task = record['task']; key = task_key(task)
|
|
receipt = record['brain_process_receipt_path']
|
|
require(receipt not in seen_receipts, 'Probe execution reused across snapshots')
|
|
seen_receipts.add(receipt)
|
|
csid = inputs['task_inputs'][task['task_id']]
|
|
recipe = task['recipe']
|
|
expected_descriptor = dict(mode=task['mode'], cue=recipe['cue'], seed=recipe['input']['seed'], steps=1200,
|
|
cs_start_tick=0, cs_stop_tick_exclusive=1000, source_stream_id=recipe['input']['stream_id'],
|
|
table_id=task['mode'] + '--' + recipe['cue'])
|
|
require(csid == sha(expected_descriptor) and inputs['descriptors'][csid] == expected_descriptor,
|
|
'Evaluation seed/stream/clock mapping differs from original plan')
|
|
source = exp / f'inputs/generated/{csid}.npz'
|
|
generated = reader.json(exp / f'inputs/generated/{csid}.json')
|
|
require(generated['descriptor'] == inputs['descriptors'][csid] and generated['requests_sha256'] == reader.check(source)['sha256'],
|
|
'Actual evaluation input descriptor differs')
|
|
case = exp / 'cases' / key
|
|
binding = reader.json(case / 'input-binding.json')
|
|
require(binding['CS_source'] == dict(path=str(source), sha256=reader.check(source)['sha256']) and
|
|
binding['CS_descriptor_id'] == csid and binding['task_id'] == task['task_id'], 'Probe shared input binding differs')
|
|
requests, cs, us = (reader.arrays(path) for path in (case / 'requests.npz', source, case / 'us_requests.npz'))
|
|
require(set(requests) == set(cs) == set(us) == {'tick', 'neuron_idx'} and
|
|
all(not len(v) for v in us.values()) and all(np.array_equal(requests[k], cs[k]) for k in requests), 'Probe includes US or changed CS')
|
|
state = reader.arrays(Path(record['checkpoint_dir']) / 'state.npz')
|
|
actual = M.validate_state(state, nodes=nodes, steps=1200)
|
|
require(actual['hashes'] == record['brain_audit']['state_raw_sha256'], 'Actual probe raw arrays differ')
|
|
return state
|
|
|
|
for item in plan['evaluation_inputs']:
|
|
record = probes[(item['block_id'], 'paired', 0, item['cue'])]
|
|
csid = inputs['task_inputs'][record['task']['task_id']]
|
|
input_hashes[(item['block_id'], item['cue'])] = reader.check(exp / f'inputs/generated/{csid}.npz')['sha256']
|
|
registry = build_registry(full, plan, nodes=nodes, populations=populations, population_binding_sha=population_binding_sha,
|
|
regsha=registration_sha256, input_hashes=input_hashes)
|
|
authority = dict(schema=G.AUTHORITY_SCHEMA, registry_sha256=G.descriptor_sha256(registry),
|
|
verification_receipt_sha256=sha(final_ledger), execution_inventory_sha256=inventory_sha,
|
|
population_binding_sha256=population_binding_sha)
|
|
result = scientific_reports(probes, arms, registry=registry, authority=authority, nodes=nodes, populations=populations, load_state=load_state)
|
|
require(len(seen_receipts) == 3920, 'Not every independently executed probe was analyzed')
|
|
result['legacy_H'] = legacy_audit(result['cells'], gates['H_mapping'])
|
|
return dict(schema='normal-scientific-analysis-v1', status='CONDITIONAL-PENDING-OUTER-INTEGRITY',
|
|
registration_sha256=registration_sha256, normal_plan_sha256=plan['payload_sha256'], registry=registry, authority=authority,
|
|
analysis=result, analyzed_probe_calls=3920, curve_points=1960, consumed_file_count=len(reader.seen),
|
|
subsequent_assays_status='NOT-RUN', subsequent_assays_authorized=False, candidate_generated=False,
|
|
learning_pass_claimed=False, scientific_learning_pass_claimed=False, data_backed_claimed=False,
|
|
external_full_tree_anatomy_historical_integrity_verified_here=False,
|
|
retained_raw_evidence='original state and receipt files; terminal raw metric vectors recomputable, not duplicated in report')
|