Files
2026-09-30 13:18:18 +08:00

397 lines
22 KiB
Python

"""Phase 4AK orchestration only: call hash-pinned, unchanged AJ functions.
No LIF, APL, queue, or input-generation equation is implemented here. Fresh
processes resolve AJ's one unqualified import to the pinned AJ timing_queue.
The D18 control adapter compares evidence; it never changes a case to D0.
"""
from __future__ import annotations
import hashlib
import importlib.util
import json
from pathlib import Path
import re
import sys
import traceback
AJ_PINS = {
'gpu_timing.py': 'af1975ba5e5e700208c0a598d3cbe648a3cf10885c6231239b6e4b28f76d173b',
'oracle_timing.py': '39d8c47c94bc2faddf016864a43e7715907fb987f56f7b27098060a88851f6be',
'compare_timing.py': '9010f070fba9b7c9cf2d86187ee2abc57f3ac606ebc08a42baad4f3922b51734',
'timing_queue.py': '54df6d4818e2a0c26e96fb0aaf5d9b71cd964981706da937562d48af79aca14e',
}
COMPONENT_SHA256 = '5cfc9a4505da6b8fb4e7d2617cdaedaa8475ed46a11ec9bdf0d4d25b1d22c2d3'
COHORTS = ('seed_validation', 'novel_to_aj')
GROUPS = ('controls',) + COHORTS
STAGES = ('controls', 'seed-probes', 'seed-validation', 'novel-probes', 'novel-validation')
_BUDGET_KEYS = frozenset(('budget_root', 'output_budget_bytes'))
_LOADED = {}
def require(ok, message):
if not ok:
raise ValueError(message)
def read(path):
return json.loads(Path(path).read_text(encoding='utf-8'))
def digest(path):
result = hashlib.sha256()
with Path(path).open('rb') as stream:
for block in iter(lambda: stream.read(8 << 20), b''):
result.update(block)
return result.hexdigest()
def save(path, report):
if path is None:
return
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
with path.open('x', encoding='utf-8') as stream:
json.dump(report, stream, ensure_ascii=False, indent=2, sort_keys=True, allow_nan=False)
stream.write('\n')
def load_checked(name, path, expected_hash):
"""Execute the verified bytes with importlib metadata, without old pyc writes."""
path = Path(path).absolute()
data = path.read_bytes()
require(hashlib.sha256(data).hexdigest() == expected_hash, 'Sealed source SHA mismatch: ' + str(path))
if name in sys.modules:
existing = sys.modules[name]
require(Path(getattr(existing, '__file__', '')).absolute() == path and
_LOADED.get(name) == (path, expected_hash, existing),
'Refusing an existing unverified module name: ' + name)
return existing
spec = importlib.util.spec_from_file_location(name, path)
require(spec is not None and spec.loader is not None, 'Cannot create sealed module specification')
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
try:
exec(compile(data, str(path), 'exec'), module.__dict__)
except BaseException:
sys.modules.pop(name, None)
raise
# Keep verification metadata here, not in the sealed module's globals.
_LOADED[name] = (path, expected_hash, module)
return module
def sealed(filename, aj_model=None):
require(filename in AJ_PINS, 'Unregistered AJ module')
if aj_model is None:
from context import AJ_MODEL
aj_model = AJ_MODEL
directory = Path(aj_model)
if filename == 'gpu_timing.py':
load_checked('timing_queue', directory / 'timing_queue.py', AJ_PINS['timing_queue.py'])
name = 'phase4ak_sealed_aj_' + Path(filename).stem
return load_checked(name, directory / filename, AJ_PINS[filename])
def case_spec(case_dir):
spec = read(Path(case_dir) / 'spec.json')
require(isinstance(spec.get('name'), str) and re.fullmatch(r'[A-Za-z0-9_-]+', spec['name']), 'Unsafe case name')
require(type(spec.get('apl_delay_steps')) is int and spec['apl_delay_steps'] == 18, 'AK permits fixed D18 only')
require(spec.get('steps') == 1000 and type(spec['steps']) is int and spec.get('dt_ms') == .1 and
spec.get('duration_ms') == 100.0 and spec.get('record_history') is False,
'AK requires the unchanged fullgraph 100 ms window')
require(spec.get('training') is False and spec.get('poi_used', False) is False and
spec.get('precision') == 'float64' and spec.get('apl_precision') == 'float32',
'Frozen no-learning / no-poi / precision scope changed')
require(spec.get('apl') == {'tau_ms': 20, 'gain': .01, 'normalization': 1}, 'Frozen APL parameters changed')
for field in ('graph_dir', 'apl_bundle_dir', 'coefficients_path', 'science_python', 'legacy_apl_worker'):
require(field in spec and Path(spec[field]).is_absolute(), 'Missing absolute path: ' + field)
require(isinstance(spec.get('model'), dict), 'Missing original author parameter dictionary')
return spec
def _new_output(case_dir, out_dir):
spec = case_spec(case_dir)
output = Path(out_dir).absolute()
budget_root = Path(spec['budget_root']).absolute()
require(type(spec.get('output_budget_bytes')) is int and spec['output_budget_bytes'] > 0,
'Missing positive output budget')
require(output.is_relative_to(budget_root) and output != budget_root and not output.exists(),
'Output must be a new exclusive child of this case budget root')
return spec
def _receipt(output, backend, filename, aj_model):
module = sealed(filename, aj_model)
require(digest(module.__file__) == AJ_PINS[filename], 'Sealed runtime changed during invocation')
receipt = {'backend': backend, 'sealed_runtime': str(module.__file__),
'sealed_runtime_sha256': AJ_PINS[filename], 'apl_delay_steps': 18,
'old_module_scientific_globals_changed': False, 'dynamics_copied_or_reimplemented': False,
'scope': 'Unchanged AJ runtime called with new case/output/build arguments; not a new numerical model'}
save(Path(output) / 'runtime-bridge.json', receipt)
def run_gpu(case_dir, out_dir, build_root, *, aj_model=None):
spec = _new_output(case_dir, out_dir)
build_root = Path(build_root).absolute()
require(build_root.is_relative_to(Path(spec['budget_root']).absolute()) and
build_root != Path(spec['budget_root']).absolute(), 'Build cache must stay in the new experiment')
module = sealed('gpu_timing.py', aj_model)
result = module.run_gpu(Path(case_dir), Path(out_dir), build_root)
_receipt(out_dir, 'gpu', 'gpu_timing.py', aj_model)
return result
def run_oracle(case_dir, out_dir, *, aj_model=None):
_new_output(case_dir, out_dir)
require('torch' not in sys.modules, 'Pinned Brian process must not import Torch')
module = sealed('oracle_timing.py', aj_model)
result = module.run_case(Path(case_dir), Path(out_dir))
_receipt(out_dir, 'oracle', 'oracle_timing.py', aj_model)
return result
def compare(case_dir, gpu_dir, cpu_dir, out_json=None, *, aj_model=None):
case_spec(case_dir)
return sealed('compare_timing.py', aj_model).compare(case_dir, gpu_dir, cpu_dir, out_json)
def compare_gpu_repeats(first, second, out_json=None, *, aj_model=None):
require(all(read(Path(path) / 'summary.json').get('apl_delay_steps') == 18 for path in (first, second)),
'Repeat evidence must be fixed D18')
return sealed('compare_timing.py', aj_model).compare_gpu_repeats(first, second, out_json)
def component(output, bundle, out_json, *, ag_model=None):
if ag_model is None:
from context import AG_MODEL
ag_model = AG_MODEL
module = load_checked('phase4ak_sealed_ag_components', Path(ag_model) / 'validate_components.py', COMPONENT_SHA256)
return module.validate(Path(output), Path(bundle), Path(out_json))
def control_inputs(newcase, oldcase):
"""Only budget metadata may differ; all saved request/drive bytes are identical."""
newcase, oldcase = Path(newcase), Path(oldcase)
fresh, old = case_spec(newcase), case_spec(oldcase)
require({k: v for k, v in fresh.items() if k not in _BUDGET_KEYS} ==
{k: v for k, v in old.items() if k not in _BUDGET_KEYS},
'D18 control scientific specification changed beyond the two budget fields')
evidence = {str(folder / 'spec.json'): digest(folder / 'spec.json') for folder in (newcase, oldcase)}
files = ['requests.npz', 'pn-drive.npz']
require((newcase / 'arrays.npz').exists() == (oldcase / 'arrays.npz').exists(), 'Control initial-state presence changed')
if (newcase / 'arrays.npz').exists():
files.append('arrays.npz')
for name in files:
first, second = digest(newcase / name), digest(oldcase / name)
require(first == second, 'Control input byte mismatch: ' + name)
evidence[str(newcase / name)], evidence[str(oldcase / name)] = first, second
return fresh, old, evidence
def control_identity(newdir, olddir, newcase, oldcase, out_json=None, *, aj_model=None):
"""D18 scientific identity using unchanged AJ comparison primitives.
Unlike the AJ D0-vs-AG adapter, this checks real D18 applied/pending files.
Input SHA fields are checked against each real spec, not compared after
deleting metadata. Only build/cache paths and the two budget fields are
permitted to differ; all numerical/timing/component evidence stays exact.
"""
require(out_json is None or not Path(out_json).exists(), 'Refusing existing control report')
aj = sealed('compare_timing.py', aj_model)
checks, differences, used = {}, {}, set()
report = {'kind': 'fixed-D18 same-input sealed-AJ control identity', 'pass_all': False,
'new_output': str(newdir), 'old_output': str(olddir), 'new_case': str(newcase), 'old_case': str(oldcase),
'apl_delay_steps': 18, 'tolerance': None, 'signed_zero_included': True,
'dynamics_copied_or_reimplemented': False, 'old_globals_patched': False,
'independent_CPU_all_components_claim': False,
'allowed_case_metadata_differences': sorted(_BUDGET_KEYS),
'build_and_wall_metadata_compared': False}
try:
spec, old_spec, inputs = control_inputs(newcase, oldcase)
used.update(Path(path) for path in inputs)
checks['control_input_spec_and_bytes'] = True
steps, n, edges = spec['steps'], 166700, 25582938
left = aj._load_run(Path(newdir), 'fresh_gpu', n, edges, steps, False, spec['name'], 18, used, checks)
right = aj._load_run(Path(olddir), 'sealed_gpu', n, edges, steps, False, spec['name'], 18, used, checks)
aj._pair_outputs(left, right, checks, differences, n, steps, False)
for role, run, case, case_input in (('fresh_gpu', left, Path(newcase), spec),
('sealed_gpu', right, Path(oldcase), old_spec)):
aj._timing_sequence_checks(run['apl']['inhibition'], run['apl']['applied'], run['pending'],
18, role, checks, differences)
aj._timing_metadata_checks(run, role, 18, steps, checks)
expected = {name: digest(case / name) for name in ('spec.json', 'requests.npz')}
if (case / 'arrays.npz').exists():
expected['arrays.npz'] = digest(case / 'arrays.npz')
weight, coeff = Path(case_input['graph_dir']) / 'weights.npy', Path(case_input['coefficients_path'])
used.update((weight, coeff))
expected.update({'base_weights.npy': digest(weight), 'coefficients.json': digest(coeff)})
checks[role + '.actual_input_hashes'] = run['summary'].get('input_sha256') == expected
stable = ('case', 'complete', 'pass_all', 'steps', 'completed_ticks', 'nodes', 'edges', 'precision',
'apl_precision', 'state_units', 'total_spikes', 'requested', 'accepted_requests', 'rejected_requests',
'arriving_edges', 'accepted_arriving_edges', 'counts_match_sparse_events', 'all_finite', 'record_history',
'apl_fast_edges_zeroed', 'kc_targets_present', 'effective_weights_raw_sha256', 'apl_schedule',
'apl_si_conversion', 'sealed_ae_backend_sha256', 'learning', 'apl_delay_steps', 'apl_timing',
'source_provenance', 'pending_generated_count', 'pending_initial_zero_prefix', 'pending_tail_shape',
'future_generated_decay_tail_included')
for key in stable:
checks['summary.' + key] = key in left['summary'] and key in right['summary'] and left['summary'][key] == right['summary'][key]
checks['timing_audit.parsed_exact'] = left['timing'] == right['timing']
report.update(case=spec['name'], steps=steps, nodes=n, edges=edges,
scope='Six state arrays, every tick state/queue/request, generated/applied/pending APL, component and timing metadata')
except Exception as error:
report['error'] = {'type': type(error).__name__, 'message': str(error)}
return aj._finish(report, checks, differences, used, out_json)
def inventories(exp, cfg):
"""Validate static cells before choosing any dynamic case; no old dynamic read."""
exp = Path(exp)
controls = read(exp / 'protocol/control-cases.json')
records = read(exp / 'protocol/validation-cases.json')
require(isinstance(controls, list) and len(controls) == 2 and
[row['name'] for row in controls] == cfg['control_cases'], 'Unexpected control inventory')
for row in controls:
require(re.fullmatch(r'[A-Za-z0-9_-]+', row['name']) and
all(Path(row[key]).is_absolute() for key in ('source_case', 'source_output')), 'Invalid control identity/path')
design = cfg['validation']
require(design['cohorts'] == list(COHORTS) and len(design['seeds']) == 5 and len(set(design['seeds'])) == 5 and
design['modes'] == ['physiological', 'matched_energy'] and design['odor_count'] == 6 and
design['repeat_first_seed_each_odor'] is True, 'Fixed validation design changed')
require(isinstance(records, list) and len(records) == 120, 'Expected 120 predeclared cases')
cells, names = set(), set()
for row in records:
key = (row['cohort'], row['mode'], row['odor_index'], row['seed'])
require(re.fullmatch(r'[A-Za-z0-9_-]+', row['name']) and row['name'] not in names and key not in cells,
'Duplicate/unsafe case identity')
require(type(row['odor_index']) is int and type(row['seed']) is int, 'Noninteger odor/seed')
cells.add(key); names.add(row['name'])
expected = {(cohort, mode, oi, seed) for cohort in COHORTS for mode in design['modes']
for oi in range(6) for seed in design['seeds']}
require(cells == expected, 'Missing or substituted validation cell')
return controls, records
def cohort_rows(records, cfg, cohort):
require(cohort in COHORTS, 'Unknown validation cohort')
design = cfg['validation']
return sorted((row for row in records if row['cohort'] == cohort),
key=lambda row: (design['modes'].index(row['mode']), row['odor_index'], design['seeds'].index(row['seed'])))
def probe_rows(records, cfg, cohort):
rows = [row for row in cohort_rows(records, cfg, cohort)
if row['odor_index'] == 0 and row['seed'] == cfg['validation']['seeds'][0]]
require(len(rows) == 2 and [row['mode'] for row in rows] == cfg['validation']['modes'], 'Fixed probe selection changed')
return rows
def ensure_unstopped(exp):
markers = sorted((Path(exp) / 'audit').glob('stopped-*.json'))
require(not markers, 'A prior stop marker forbids all new scientific actions: ' + ', '.join(path.name for path in markers))
def guarded_action(exp, backend, group, name, repeat, action):
"""Persist failure after invoking an authorized scientific action, even standalone.
The caller performs input authorization and external idle preflight first.
A failure here is an execution/audit failure, not evidence of instability.
"""
require(backend in ('gpu', 'oracle', 'component') and group in GROUPS and
isinstance(name, str) and re.fullmatch(r'[A-Za-z0-9_-]+', name) and
type(repeat) is int and repeat in (0, 1), 'Invalid guarded action identity')
ensure_unstopped(exp)
try:
return action()
except Exception as error:
save(Path(exp) / 'audit' / f'stopped-case-{backend}-{group}-{name}-r{repeat}.json', {
'stage': 'case', 'backend': backend, 'group': group, 'case': name, 'repeat': repeat,
'type': type(error).__name__, 'message': str(error), 'traceback': traceback.format_exc(),
'stop_kind': 'execution_or_numerical_failure', 'scientific_action_invoked': True,
'numerical_instability_inferred_from_execution_error': False,
'later_stages_not_permitted': True, 'partial_outputs_preserved': True,
'parameters_changed': False, 'replacement_seeds_or_odors_selected': False})
raise
def validate_summary_coverage(summary, exp, cfg, cohort, *, representation=False):
"""Bind the unlock to this cohort's exact 60 base / 12 repeat / 72 timing cells."""
_, records = inventories(exp, cfg)
rows = cohort_rows(records, cfg, cohort)
expected = {row['name'] for row in rows}
repeated = {row['name'] for row in rows if row['seed'] == cfg['validation']['seeds'][0]}
timing = {(name, 0) for name in expected} | {(name, 1) for name in repeated}
require(summary.get('cohort') == cohort and summary.get('phase') == '4AK' and
summary.get('apl_delay_steps') == 18 and summary.get('seeds') == cfg['validation']['seeds'] and
summary.get('duration_ms') == 100.0, 'Validation summary identity/window differs')
require(summary.get('expected_base_trials') == summary.get('valid_base_trials') == 60 and
summary.get('expected_repeats') == summary.get('checked_repeats') == 12 and
summary.get('checked_timing_trials') == 72, 'Incomplete validation coverage')
base_rows, repeat_rows, timing_rows = (summary.get(key, []) for key in ('runs', 'repeats', 'timing_checks'))
require(len(base_rows) == 60 and {row['case'] for row in base_rows} == expected and
all(row.get('cohort') == cohort and row.get('validation_cohort') == cohort for row in base_rows),
'Base-trial identities differ from the fixed cohort inventory')
require(len(repeat_rows) == 12 and {row['case'] for row in repeat_rows} == repeated and
all(row.get('pass_all') is True for row in repeat_rows), 'Exact-repeat identities/coverage failed')
require(len(timing_rows) == 72 and {(row['case'], row['repeat']) for row in timing_rows} == timing and
all(type(row['repeat']) is int and row.get('pass_all') is True and row.get('apl_delay_steps') == 18
and row.get('steps') == 1000 for row in timing_rows), 'Timing identities/coverage failed')
proof = summary.get('input_output_provenance', [])
require(len(proof) == 72 and {(row['case'], row['repeat']) for row in proof} == timing,
'Per-run evidence provenance does not cover exactly the registered cells')
require(summary.get('prerequisite_input_sha256', {}).get('validation_inventory') ==
digest(Path(exp) / 'protocol/validation-cases.json'), 'Summary was derived from a different validation inventory')
require(summary.get('execution_gate_pass') is True and summary.get('numerical_integrity_pass') is True and
summary.get('exact_repeat_pass') is True, 'Validation execution/numerical/repeat gate failed')
require(set(summary.get('modes', {})) == set(cfg['validation']['modes']), 'Incomplete mode summaries')
if representation:
require(summary.get('representation_gate_pass') is True and summary.get('pass_all') is True and
all(summary['modes'][mode].get('gate') is True for mode in cfg['validation']['modes']),
'Seed representation summary does not authorize novel dynamics')
def gate_pass(exp, stage, *, representation=False, cfg=None):
report = read(Path(exp) / 'audit' / (stage + '-gate.json'))
require(report.get('stage') == stage and report.get('pass_all') is True, 'Required execution gate failed: ' + stage)
if representation:
require(stage == 'seed-validation', 'Only the complete seed block unlocks novel trajectories')
require(report.get('execution_gate_pass') is True and report.get('representation_gate_pass') is True,
'Complete seed representation/execution gate required before novel dynamics')
if cfg is None:
from context import CFG
cfg = CFG
path = Path(exp) / 'validation/seed_validation/summary.json'
require(report.get('cohort') == 'seed_validation' and
report.get('summary') == 'validation/seed_validation/summary.json' and
report.get('summary_sha256') == digest(path), 'Seed gate is not bound to this exact summary')
require(report.get('validation_inventory_sha256') == digest(Path(exp) / 'protocol/validation-cases.json'),
'Seed gate inventory digest differs')
validate_summary_coverage(read(path), exp, cfg, 'seed_validation', representation=True)
return report
def authorize_case(exp, cfg, group, name, backend, repeat):
ensure_unstopped(exp)
require(group in GROUPS and backend in ('gpu', 'oracle', 'component') and type(repeat) is int and repeat in (0, 1),
'Invalid runtime action')
controls, records = inventories(exp, cfg)
rows = controls if group == 'controls' else cohort_rows(records, cfg, group)
selected = [row for row in rows if row['name'] == name]
require(len(selected) == 1, 'Case not in the registered group inventory')
row = selected[0]
if group == 'controls':
require(repeat == 0 and backend in ('gpu', 'component'), 'Controls are single fresh GPU identity runs')
else:
gate_pass(exp, 'controls')
if group == 'novel_to_aj':
gate_pass(exp, 'seed-validation', representation=True, cfg=cfg)
probes = {item['name'] for item in probe_rows(records, cfg, group)}
probe_stage = 'seed-probes' if group == 'seed_validation' else 'novel-probes'
if name not in probes or repeat:
gate_pass(exp, probe_stage)
if backend in ('oracle', 'component'):
require(name in probes and repeat == 0, 'Oracle/component scope is the two fixed probes per cohort')
if repeat:
require(backend == 'gpu' and row['seed'] == cfg['validation']['seeds'][0], 'Only declared first-seed repeats')
return row