Seal AO interface failure and verified isolated postprocessing diagnosis
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
"""Pure pre-outcome DRAFT snapshot policy; no I/O, inputs, execution or RNG.
|
||||
|
||||
The caller must supply the complete existing trial_plan payload. This module
|
||||
does not select a master seed. Its fixed policy choices are newly proposed
|
||||
planning choices, not inherited biological measurements or scientific gates.
|
||||
|
||||
build_evaluation_policy(trial_payload) returns a config directly consumable by
|
||||
evaluation_plan.build_evaluation_plan, plus a separate ordered prefix audit.
|
||||
Every snapshot boundary is AFTER the complete selected epoch and its scheduled
|
||||
delta update, not immediately after the US tick. All returned timing and counts
|
||||
describe planned exposures, never observed or accepted DAN spikes.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
import evaluation_plan
|
||||
import trial_plan
|
||||
|
||||
|
||||
SCHEMA = "phase4an-evaluation-policy-draft-v1"
|
||||
ALGORITHM = "fixed-ordered-CS-prefix-completed-epoch-v1"
|
||||
POINTS = (0, 1, 2, 5, 10, 20, 50)
|
||||
CS_FIELDS = frozenset(("cue", "odor", "occurrence", "stream_id", "source_stream_id",
|
||||
"seed", "seed_bits", "seed_conversion", "role", "start_tick", "stop_tick_exclusive"))
|
||||
IDENTITY_FIELDS = tuple(sorted(CS_FIELDS - {"stream_id"}))
|
||||
SAME_POSITION_ARMS = frozenset(("d_zero", "frozen", "random_reward", "cs_only"))
|
||||
|
||||
|
||||
def _require(condition, message):
|
||||
if not condition:
|
||||
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("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def _registry_index(trial):
|
||||
manifest = trial["seed_manifest"]
|
||||
independent = {(r["split"], tuple(r["key"])): r for r in manifest["independent_streams"]}
|
||||
aliases = {(r["split"], tuple(r["key"])): r for r in manifest["aliases"]}
|
||||
_require(len(independent) == len(manifest["independent_streams"]) and
|
||||
len(aliases) == len(manifest["aliases"]) and not (independent.keys() & aliases.keys()),
|
||||
"Duplicate or overlapping seed registry keys")
|
||||
return independent, aliases
|
||||
|
||||
|
||||
def _stream_id(master, key):
|
||||
return "training:" + _sha([trial_plan.SCHEMA, "stream-id", master, "training", list(key)])
|
||||
|
||||
|
||||
def _cs_identity(cs, *, block_id, assay, arm, base_arm, master, registry):
|
||||
"""Verify actual alias source/seed; omit only the control's own alias ID."""
|
||||
_require(type(cs) is dict and set(cs) == CS_FIELDS, "CS binding schema differs")
|
||||
_require(cs["cue"] in trial_plan.CUES and cs["odor"] == trial_plan.ODORS[cs["cue"]] and
|
||||
type(cs["occurrence"]) is int and 0 <= cs["occurrence"] < 50 and
|
||||
type(cs["seed"]) is int and 0 <= cs["seed"] < 2**63 and
|
||||
type(cs["seed_bits"]) is int and cs["seed_bits"] == 63 and cs["role"] == "CS" and
|
||||
type(cs["start_tick"]) is int and cs["start_tick"] == 0 and
|
||||
type(cs["stop_tick_exclusive"]) is int and cs["stop_tick_exclusive"] == 1000 and
|
||||
cs["seed_conversion"] == "exact integer; no truncation or addition",
|
||||
"CS cue/occurrence/full seed/role/timing differs")
|
||||
key = (block_id, assay, "CS", cs["cue"], f'{cs["occurrence"]:02d}', arm)
|
||||
source_key = key[:-1] + (base_arm,)
|
||||
independent, aliases = registry
|
||||
source = independent.get(("training", source_key))
|
||||
_require(source is not None and source["seed"] == cs["seed"] and
|
||||
cs["source_stream_id"] == _stream_id(master, source_key) and
|
||||
cs["stream_id"] == _stream_id(master, key), "CS seed/source stream authority differs")
|
||||
if arm == base_arm:
|
||||
_require(("training", key) not in aliases and cs["stream_id"] == cs["source_stream_id"],
|
||||
"Original CS must be its own independent source")
|
||||
else:
|
||||
alias = aliases.get(("training", key))
|
||||
_require(alias is not None and alias["source_split"] == "training" and
|
||||
alias["source_key"] == list(source_key) and alias["seed"] == cs["seed"] and
|
||||
alias["independent_replicate"] is False,
|
||||
"CS alias is not registered to this block/assay/cue/occurrence source")
|
||||
return {key: cs[key] for key in IDENTITY_FIELDS}
|
||||
|
||||
|
||||
def _prefixes(arm, *, block_id, assay, base_arm, master, registry):
|
||||
"""Return verified ordered CS records indexed by actual completed epochs."""
|
||||
identities = []
|
||||
prefixes = [[]]
|
||||
for epoch in arm["epochs"]:
|
||||
if epoch["cs"] is not None:
|
||||
identities.append(_cs_identity(epoch["cs"], block_id=block_id, assay=assay,
|
||||
arm=arm["arm"], base_arm=base_arm, master=master, registry=registry))
|
||||
prefixes.append(list(identities))
|
||||
if arm["arm"] == "dan_only":
|
||||
_require(not identities, "DAN-only must contain no delivered CS")
|
||||
else:
|
||||
_require(len(identities) == 100, "Every CS-present arm must retain all 100 CS epochs")
|
||||
counters = Counter()
|
||||
for row in identities:
|
||||
_require(row["occurrence"] == counters[row["cue"]], "CS occurrence order is not canonical")
|
||||
counters[row["cue"]] += 1
|
||||
_require(dict(counters) == {"A": 50, "B": 50}, "Both original odors require 50 CS occurrences")
|
||||
return prefixes
|
||||
|
||||
|
||||
def _reinforcement_boundary(arm, point):
|
||||
"""The whole epoch containing the kth requested US; terminal includes all."""
|
||||
if point == 0:
|
||||
return 0
|
||||
epochs = arm["epochs"]
|
||||
us_indices = [i for i, e in enumerate(epochs) if e["us"]["requested"]]
|
||||
_require(len(us_indices) == 50, "Reference arm must have exactly 50 requested US epochs")
|
||||
return len(epochs) if point == 50 else us_indices[point - 1] + 1
|
||||
|
||||
|
||||
def _exposure(arm, count, cs_prefix):
|
||||
epochs = arm["epochs"][:count]
|
||||
return dict(completed_epochs=count, CS_epochs=len(cs_prefix),
|
||||
CS_counts_by_cue={cue: sum(c["cue"] == cue for c in cs_prefix) for cue in trial_plan.CUES},
|
||||
ordered_CS_source_identity_sha256=_sha(cs_prefix),
|
||||
requested_US_epochs=sum(e["us"]["requested"] for e in epochs),
|
||||
enabled_nonzero_D_update_opportunities=sum(e["update_enabled"] and e["D_command"] != 0 for e in epochs),
|
||||
elapsed_training_ticks=sum(e["steps"] for e in epochs),
|
||||
actual_accepted_US_or_DAN_spikes_known=False)
|
||||
|
||||
|
||||
def _suite_policy(block, suite, master, registry):
|
||||
"""Independent prefix/accounting construction, separate from evaluation_plan."""
|
||||
assay, block_id = suite["assay"], block["block_id"]
|
||||
_require(assay in ("normal", "delayed", "reversal"), "Unexpected assay")
|
||||
expected_arms = trial_plan.REVERSAL_ARMS if assay == "reversal" else trial_plan.ARMS
|
||||
_require([a["arm"] for a in suite["arms"]] == list(expected_arms), "Missing, reordered or duplicate control arms")
|
||||
arms = {a["arm"]: a for a in suite["arms"]}
|
||||
base_name = "reversal" if assay == "reversal" else "paired"
|
||||
base = arms[base_name]
|
||||
_require(len(base["epochs"]) == 100, "Base arm must contain all 100 epochs")
|
||||
prefixes = {name: _prefixes(arm, block_id=block_id, assay=assay, base_arm=base_name,
|
||||
master=master, registry=registry) for name, arm in arms.items()}
|
||||
base_full = prefixes[base_name][-1]
|
||||
for name, arm in arms.items():
|
||||
_require(len(arm["epochs"]) == (150 if name == "unpaired" else 100), "Unexpected epoch coverage")
|
||||
if name == "dan_only":
|
||||
for i, epoch in enumerate(arm["epochs"]):
|
||||
original = base["epochs"][i]
|
||||
_require(epoch["cs"] is None and
|
||||
epoch["yoked_cue_removed"] == original["cs"]["cue"] and
|
||||
type(epoch["yoked_occurrence_removed"]) is int and
|
||||
epoch["yoked_occurrence_removed"] == original["cs"]["occurrence"] and
|
||||
_canonical(epoch["us"]) == _canonical(original["us"]) and
|
||||
epoch["steps"] == original["steps"],
|
||||
"DAN-only omitted CS/yoked US/epoch position differs from paired")
|
||||
else:
|
||||
_require(_canonical(prefixes[name][-1]) == _canonical(base_full),
|
||||
"Actual ordered CS cue/occurrence/seed/source sequence differs")
|
||||
if name != "unpaired":
|
||||
_require(all(_canonical(a) == _canonical(b)
|
||||
for a, b in zip(prefixes[name], prefixes[base_name])),
|
||||
"CS sequence differs at a paired epoch position")
|
||||
|
||||
bindings, audits = [], []
|
||||
for name in expected_arms:
|
||||
arm = arms[name]
|
||||
previous = -1
|
||||
for point in POINTS:
|
||||
base_count = _reinforcement_boundary(base, point)
|
||||
expected_prefix = prefixes[base_name][base_count]
|
||||
if assay == "reversal":
|
||||
count = _reinforcement_boundary(arm, point)
|
||||
alignment = "own reinforced-US ordinal; intermediate CS prefixes need not match across arms"
|
||||
elif name == "unpaired":
|
||||
if point == 0:
|
||||
count = 0
|
||||
elif point == 50:
|
||||
count = len(arm["epochs"])
|
||||
else:
|
||||
matches = [i for i, prefix in enumerate(prefixes[name]) if len(prefix) == len(expected_prefix)]
|
||||
_require(bool(matches), "Unpaired arm never reaches the required CS prefix")
|
||||
count = matches[0]
|
||||
alignment = "earliest ordered-CS-prefix boundary; terminal includes all 150 epochs"
|
||||
else:
|
||||
count = base_count
|
||||
alignment = ("paired yoked epoch positions; zero delivered CS, not equal CS exposure"
|
||||
if name == "dan_only" else "exact paired completed-epoch and ordered-CS prefix")
|
||||
_require(count > previous, "Policy snapshot epochs must advance strictly")
|
||||
previous = count
|
||||
own_prefix = prefixes[name][count]
|
||||
same_cs = _canonical(own_prefix) == _canonical(expected_prefix)
|
||||
if assay != "reversal" and name != "dan_only":
|
||||
_require(same_cs, "Ordered CS prefix mismatch at a control checkpoint")
|
||||
if name == "unpaired" and point not in (0, 50):
|
||||
_require(count > 0 and len(prefixes[name][count - 1]) < len(expected_prefix),
|
||||
"Unpaired snapshot is not the earliest matching completed-epoch boundary")
|
||||
actual = _exposure(arm, count, own_prefix)
|
||||
reference = _exposure(base, base_count, expected_prefix)
|
||||
binding = dict(block_id=block_id, assay=assay, arm=name,
|
||||
checkpoint=point, completed_epochs=count)
|
||||
bindings.append(binding)
|
||||
audits.append(dict(binding=binding, alignment=alignment,
|
||||
reference_arm=base_name, reference_completed_epochs=base_count,
|
||||
boundary="after complete epoch and scheduled delta update; not at the US tick",
|
||||
terminal_includes_all_epochs=point == 50,
|
||||
actual=actual, reference=reference,
|
||||
ordered_CS_prefix_exact=same_cs if name != "dan_only" else None,
|
||||
CS_exposure_equal=same_cs if name != "dan_only" else False,
|
||||
DAN_only_yoke_verified=name == "dan_only",
|
||||
requested_US_count_equal_to_reference=actual["requested_US_epochs"] == reference["requested_US_epochs"],
|
||||
requested_US_count_difference=actual["requested_US_epochs"] - reference["requested_US_epochs"],
|
||||
elapsed_ticks_equal_to_reference=actual["elapsed_training_ticks"] == reference["elapsed_training_ticks"],
|
||||
elapsed_ticks_difference=actual["elapsed_training_ticks"] - reference["elapsed_training_ticks"],
|
||||
nonzero_D_update_opportunity_difference=actual["enabled_nonzero_D_update_opportunities"] - reference["enabled_nonzero_D_update_opportunities"],
|
||||
all_US_timing_or_total_exposure_matched_claimed=False))
|
||||
return bindings, audits
|
||||
|
||||
|
||||
def _assemble(trial):
|
||||
registry = _registry_index(trial)
|
||||
bindings, audits = [], []
|
||||
blocks = trial["plan"]["blocks"]
|
||||
_require(len(blocks) == 40, "Exactly 40 original training blocks are required")
|
||||
for block in blocks:
|
||||
_require(block["evaluation_checkpoints"] == list(POINTS) and
|
||||
[s["assay"] for s in block["suites"]] == ["normal", "delayed", "reversal"],
|
||||
"Original assay/checkpoint coverage differs")
|
||||
for suite in block["suites"]:
|
||||
suite_bindings, suite_audits = _suite_policy(block, suite, trial["plan"]["master"], registry)
|
||||
bindings.extend(suite_bindings)
|
||||
audits.extend(suite_audits)
|
||||
config = dict(schema=evaluation_plan.CONFIG_SCHEMA, probe_steps=1200,
|
||||
cs_start_tick=0, cs_stop_tick_exclusive=1000, reversal_checkpoints=list(POINTS),
|
||||
baseline_execution="execute_each_snapshot", snapshot_binding_policy="explicit_completed_epochs",
|
||||
snapshot_bindings=bindings)
|
||||
# Check the unchanged consumer's declared schema/coverage as well as our
|
||||
# independent ordered-prefix construction. Do not generate its probes here.
|
||||
evaluation_plan._configuration(config)
|
||||
evaluation_plan._binding_inventory(trial["plan"], config)
|
||||
_require(len(bindings) == 4480 and len({_canonical(v) for v in bindings}) == 4480,
|
||||
"Incomplete or duplicate snapshot coverage")
|
||||
counts = Counter(row["assay"] for row in bindings)
|
||||
result = dict(schema=SCHEMA, status="DRAFT-NOT-REGISTERED", algorithm=ALGORITHM,
|
||||
trial_payload_sha256=trial["payload_sha256"],
|
||||
seed_manifest_sha256=trial["seed_manifest"]["payload_sha256"],
|
||||
config_sha256=_sha(config), config=config,
|
||||
audit=dict(bindings=audits,
|
||||
coverage=dict(training_blocks=40, training_replicates_per_cell=5,
|
||||
nonretention_snapshots=4480, snapshots_by_assay=dict(sorted(counts.items())),
|
||||
original_full_CS_alias_sequences_verified=True,
|
||||
new_random_streams=0, new_master_seed_selected=False),
|
||||
exposure_limits=[
|
||||
"CS-present normal/delayed controls match actual ordered CS prefixes, not just counts.",
|
||||
"US counts, nonzero-D opportunities and elapsed time are separately reported, not assumed matched.",
|
||||
"Unpaired boundaries include earlier inserted US; terminal includes all 150 epochs and trailing US.",
|
||||
"DAN-only has zero delivered CS; yoked positions are not equal CS exposure.",
|
||||
"Reversal and continuation use their own kth US; intermediate CS prefixes can differ.",
|
||||
"All snapshots are after a full epoch plus its delta update, not the US tick.",
|
||||
"Requested US is not accepted DAN activity, dopamine concentration or learning evidence."],
|
||||
unchanged_retention_scope=dict(arms=["normal/paired", "normal/frozen"],
|
||||
idle_checkpoints_seconds=[0, 1, 10],
|
||||
same_continuous_ten_second_lineage=True, probes_fork_and_do_not_advance_idle=True,
|
||||
retained_snapshot_execution_or_lineage_verified=False),
|
||||
conditional_evaluation_inventory=dict(nonretention_probes=8960,
|
||||
retention_snapshots=240, retention_probes=480, all_snapshots=4720,
|
||||
all_probes=9440, probe_steps=1200, evaluation_steps=11328000,
|
||||
includes_full_seven_point_reversal=True,
|
||||
baseline_output_deduplication=False,
|
||||
actual_execution_count=0)),
|
||||
policy_structural_checks_complete=True, choices_fixed_before_outcomes=True,
|
||||
choices_are_new_draft_proposals_not_inherited_gates=True,
|
||||
protocol_approval_claimed=False, execution_authorized=False, training_executed=False,
|
||||
inputs_generated=False, input_generator_implementation_verified=False,
|
||||
source_and_snapshot_authority_verified=False, candidate_generated=False,
|
||||
learning_pass_claimed=False, scientific_gate_added=False)
|
||||
result["payload_sha256"] = _sha(result)
|
||||
return json.loads(_canonical(result))
|
||||
|
||||
|
||||
def build_evaluation_policy(trial_payload):
|
||||
"""Return fixed DRAFT config/audit; caller provides, we never choose, master."""
|
||||
trial_plan.validate_trial_plan(trial_payload)
|
||||
return _assemble(trial_payload)
|
||||
|
||||
|
||||
def validate_evaluation_policy(payload, trial_payload):
|
||||
"""Exact reconstruction, including JSON types; a replaced self-hash is not authority."""
|
||||
_require(type(payload) is dict and "payload_sha256" in payload, "Invalid policy payload")
|
||||
_require(payload["payload_sha256"] == _sha({k: v for k, v in payload.items() if k != "payload_sha256"}),
|
||||
"Actual evaluation policy SHA mismatch")
|
||||
expected = build_evaluation_policy(trial_payload)
|
||||
_require(_canonical(payload) == _canonical(expected), "Policy config/audit/coverage differs from exact reconstruction")
|
||||
return True
|
||||
Reference in New Issue
Block a user