Seal AO interface failure and verified isolated postprocessing diagnosis

This commit is contained in:
Digital Drosophila Archive
2026-09-30 17:57:02 +08:00
parent 4ff241bdc9
commit 8e9f8f4d56
126 changed files with 92124 additions and 3 deletions
@@ -0,0 +1,325 @@
"""Pure, unregistered conditioning inventory; no files, brain inputs or runs.
build_trial_plan(master) returns plan/seed_manifest/budget. validate_trial_plan
checks seed authority, alias permissions, and exact deterministic reconstruction
of the complete inventory, not just totals. A consumer must still bind the exact
63-bit seed and generator to its generated input receipt; this module cannot
certify a future consumer that silently truncates an integer.
"""
from __future__ import annotations
from collections import Counter
import hashlib
import json
import seed_registry
SCHEMA = "phase4an-trial-plan-v1-draft"
SHUFFLE_VERSION = "sha256-counter-rejection-fisher-yates-v1"
MODES = ("physiological", "matched_energy")
DAN_BRANCHES = (("PAM15", "y5B'2a"), ("PPL103", "y2a'1"))
CUES = ("A", "B")
ODORS = {"A": "4-ethylphenol", "B": "3-hexanone"}
ARMS = ("paired", "d_zero", "frozen", "random_reward", "unpaired", "cs_only", "dan_only")
REVERSAL_ARMS = ("reversal", "no_reversal")
CONTROL_ALIASES = frozenset(("d_zero", "frozen", "random_reward", "unpaired", "cs_only"))
PROBE_CHECKPOINTS = (0, 1, 2, 5, 10, 20, 50)
def _require(value, message):
if not value:
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()
class _Shuffle:
"""Explicit portable integer algorithm, unrelated to neural input RNG.
Each draw hashes canonical UTF-8 JSON [version, full63bitseed, counter].
Interpret all 256 digest bits as a big-endian unsigned integer; increment
counter even for a rejection. Reject x >= 2**256 - (2**256 % bound), then
return x % bound. Fisher-Yates descends i=n-1..1 and swaps with draw(i+1).
No Python random/NumPy algorithm, modulo bias, or seed search is used.
"""
def __init__(self, seed):
_require(type(seed) is int and 0 <= seed < 2**63, "Full 63-bit shuffle seed required")
self.seed = seed
self.counter = 0
def below(self, bound):
_require(type(bound) is int and 0 < bound <= 2**256, "Invalid shuffle bound")
limit = 2**256 - (2**256 % bound)
while True:
raw = bytes.fromhex(_sha([SHUFFLE_VERSION, self.seed, self.counter]))
self.counter += 1
value = int.from_bytes(raw, "big")
if value < limit:
return value % bound
def shuffled(self, values):
result = list(values)
for i in range(len(result) - 1, 0, -1):
j = self.below(i + 1)
result[i], result[j] = result[j], result[i]
return result
def _stream_id(master, split, key):
return split + ":" + _sha([SCHEMA, "stream-id", master, split, list(key)])
def _block_id(mode, dan, reinforced, replicate):
return f"{mode}__{dan}__reinforce_{reinforced}__rep{replicate:02d}"
def _input_key(block, assay, cue, occurrence, arm):
return (block, assay, "CS", cue, f"{occurrence:02d}", arm)
def _cs_binding(registry, block, assay, cue, occurrence, arm, base_arm):
key = _input_key(block, assay, cue, occurrence, arm)
source_key = _input_key(block, assay, cue, occurrence, base_arm)
if arm == base_arm:
seed = registry.add("training", key)
else:
seed = registry.alias("training", key, "training", source_key,
reason="Same block/assay/CS cue/occurrence; named counterfactual control")
return {"cue": cue, "odor": ODORS[cue], "occurrence": occurrence,
"stream_id": _stream_id(registry.master, "training", key),
"source_stream_id": _stream_id(registry.master, "training", source_key),
"seed": seed, "seed_bits": 63, "seed_conversion": "exact integer; no truncation or addition",
"role": "CS", "start_tick": 0, "stop_tick_exclusive": 1000}
def _ordered_cues(seed):
counts = {"A": 0, "B": 0}
result = []
for cue in _Shuffle(seed).shuffled(["A"] * 50 + ["B"] * 50):
result.append((cue, counts[cue]))
counts[cue] += 1
return result
def _epoch(registry, block, assay, arm, ordinal, cue, occurrence, base_arm,
us_ordinal, yoked_cue=None, yoked_occurrence=None):
delayed = assay == "delayed"
steps, us_tick = (1800, 1500) if delayed else (1200, 900)
requested = us_ordinal is not None
cs = None if cue is None else _cs_binding(registry, block, assay, cue, occurrence, arm, base_arm)
return {
"epoch_id": f"{block}/{assay}/{arm}/{ordinal:03d}",
"epoch_index": ordinal, "steps": steps, "duration_ms": steps // 10,
"initial_neural_state": "frozen_reset_including_KC_trace_and_both_delay_queues",
"cs": cs,
"us": {"requested": requested,
"inventory_id": None if not requested else f"{block}/{assay}/US/{us_ordinal:02d}",
"target_binding": None if not requested else block + "/confirmed-DAN-targets",
"tick": us_tick if requested else None,
"nominal_slot_tick": us_tick,
"request_rule": "fixed_single_pulse_all_bound_DAN_targets" if requested else "none",
"rng_seed": None, "rng_draws": 0},
"D_command": 1.0 if requested and arm != "d_zero" else 0.0,
"D_definition": "scheduled artificial scalar, not multiplied by acceptance",
"update_enabled": arm != "frozen",
"update_schedule": "epoch_boundary_only",
"yoked_cue_removed": yoked_cue,
"yoked_occurrence_removed": yoked_occurrence,
}
def _suite(registry, block, assay, reinforced):
base_arm = "reversal" if assay == "reversal" else "paired"
order_key = (block, assay, "cue-order")
order_seed = registry.add("design", order_key)
ordered = _ordered_cues(order_seed)
designs = [{"purpose": "cue-order", "stream_id": _stream_id(registry.master, "design", order_key),
"seed": order_seed, "algorithm": SHUFFLE_VERSION}]
random_sets = None
placements = None
if assay != "reversal":
key = (block, assay, "random-reward-placement")
seed = registry.add("design", key)
shuffle = _Shuffle(seed)
random_sets = {cue: sorted(shuffle.shuffled(range(50))[:25]) for cue in CUES}
designs.append({"purpose": "random-reward-placement", "stream_id": _stream_id(registry.master, "design", key),
"seed": seed, "algorithm": SHUFFLE_VERSION})
key = (block, assay, "unpaired-US-placement")
seed = registry.add("design", key)
placements = _Shuffle(seed).shuffled(["CS"] * 100 + ["US"] * 50)
designs.append({"purpose": "unpaired-US-placement", "stream_id": _stream_id(registry.master, "design", key),
"seed": seed, "algorithm": SHUFFLE_VERSION})
arms = []
for arm in REVERSAL_ARMS if assay == "reversal" else ARMS:
events = []
arm_reinforced = ("B" if reinforced == "A" else "A") if arm == "reversal" else reinforced
if arm == "unpaired":
cursor, us_cursor = 0, 0
for token in placements:
if token == "CS":
cue, occurrence = ordered[cursor]
cursor += 1
events.append(_epoch(registry, block, assay, arm, len(events), cue, occurrence, base_arm, None))
else:
events.append(_epoch(registry, block, assay, arm, len(events), None, None, base_arm, us_cursor))
us_cursor += 1
else:
reward_us_ids = None
if arm == "random_reward":
reward_us_ids = {(cue, occurrence): offset + i
for cue, offset in (("A", 0), ("B", 25))
for i, occurrence in enumerate(random_sets[cue])}
for cue, occurrence in ordered:
if arm == "random_reward":
us_ordinal = reward_us_ids.get((cue, occurrence))
elif arm == "cs_only":
us_ordinal = None
else:
us_ordinal = occurrence if cue == arm_reinforced else None
if arm == "dan_only":
events.append(_epoch(registry, block, assay, arm, len(events), None, None, base_arm,
us_ordinal, yoked_cue=cue, yoked_occurrence=occurrence))
else:
events.append(_epoch(registry, block, assay, arm, len(events), cue, occurrence, base_arm, us_ordinal))
arms.append({
"arm": arm,
"delta_start": {"kind": "fork", "source": f"{block}/normal/paired/terminal-delta"}
if assay == "reversal" else {"kind": "zero", "source": None},
"carry_between_epochs": ["plastic_delta_only"],
"reinforced_cue": arm_reinforced if arm not in ("random_reward", "unpaired", "cs_only", "dan_only") else None,
"random_reward_occurrences": random_sets if arm == "random_reward" else None,
"epochs": events,
})
return {"assay": assay, "design_streams": designs, "arms": arms}
def _assemble(master):
registry = seed_registry.SeedRegistry(master)
blocks = []
for mode in MODES:
for dan, compartment in DAN_BRANCHES:
for reinforced in CUES:
for replicate in range(1, 6):
block = _block_id(mode, dan, reinforced, replicate)
evaluations = []
for cue in CUES:
key = _input_key(block, "evaluation", cue, 0, "base-probe")
seed = registry.add("evaluation", key)
evaluations.append({"cue": cue, "odor": ODORS[cue], "role": "CS", "seed": seed,
"seed_bits": 63, "stream_id": _stream_id(master, "evaluation", key),
"use": "same saved input across pre/post/controls; never extra training replicate"})
blocks.append({
"block_id": block, "mode": mode, "dan_type": dan,
"compartment_verbatim": compartment, "replicate": replicate,
"initial_reinforced_cue": reinforced,
"initial_neutral_cue": "B" if reinforced == "A" else "A",
"DAN_target_binding": {"binding_id": block + "/confirmed-DAN-targets",
"source": "sealed H exact confirmed mapping; not materialized by this planner",
"required_before_execution": True, "body_ids": None},
"suites": [_suite(registry, block, assay, reinforced)
for assay in ("normal", "delayed", "reversal")],
"evaluation_streams": evaluations,
"evaluation_checkpoints": list(PROBE_CHECKPOINTS),
"evaluation_policy": "separate future probe inventory required; evaluation exposure not included in epoch budget",
"retention": {"branches": ["normal/paired", "normal/frozen"],
"checkpoint_seconds": [0, 1, 10], "idle_seconds_each": 10,
"state_continuous": True, "probes_fork_and_do_not_advance_idle": True},
})
plan = {
"schema": SCHEMA, "status": "DRAFT-NOT-REGISTERED", "master": master,
"execution_authorized": False, "brain_inputs_generated": False,
"training_executed": False, "shuffle_algorithm": SHUFFLE_VERSION,
"shuffle_specification": _Shuffle.__doc__.strip(),
"input_seed_contract": "full63bit integer; no uint32 truncation, offset, modulo or hidden global RNG",
"consumer_generator_implementation_verified": False,
"fixed_odors": dict(ODORS), "dt_ms": 0.1,
"training_replicates_per_cell": 5, "blocks": blocks,
"alias_policy": "training CS only; same block/assay/cue/occurrence; named controls only",
"scheduled_US_rng": "none; deterministic pulse; design shuffle randomness is separately registered",
}
counts, steps = Counter(), 0
cs_epochs, us_epochs, effective_updates = 0, 0, 0
for block in blocks:
for suite in block["suites"]:
for arm in suite["arms"]:
counts[(suite["assay"], arm["arm"])] += len(arm["epochs"])
for epoch in arm["epochs"]:
steps += epoch["steps"]
cs_epochs += epoch["cs"] is not None
us_epochs += epoch["us"]["requested"]
effective_updates += epoch["update_enabled"] and epoch["D_command"] != 0
budget = {
"blocks": 40, "training_replicates_per_cell": 5,
"epoch_counts_by_assay_arm": [{"assay": a, "arm": b, "epochs": count}
for (a, b), count in sorted(counts.items())],
"training_control_reversal_epochs": sum(counts.values()),
"CS_epochs": cs_epochs, "US_request_epochs": us_epochs,
"enabled_nonzero_D_update_opportunities": effective_updates,
"training_control_reversal_steps": steps,
"training_control_reversal_biological_seconds": steps // 10000,
"retention_idle_seconds": 40 * 2 * 10,
"retention_idle_steps": 40 * 2 * 10 * 10000,
"minimum_biological_seconds_excluding_evaluation": steps // 10000 + 800,
"minimum_steps_excluding_evaluation": steps + 8000000,
"evaluation_epochs": None, "evaluation_biological_seconds": None,
"evaluation_probe_inventory_complete": False,
"excludes": ["evaluation probes", "implementation validation", "frozen regressions", "I/O and encoding wall time"],
"reason_not_total_runtime": "Only training/control/reversal and continuous retention idle are budgeted; never label this complete total exposure",
}
_require(budget["training_control_reversal_epochs"] == 68000 and steps == 99600000,
"Internal protocol inventory arithmetic changed")
result = {"plan": plan, "seed_manifest": registry.manifest(), "budget": budget}
result["payload_sha256"] = _sha(result)
return result
def _validate_alias_permissions(manifest):
for row in manifest["aliases"]:
key, source = row["key"], row["source_key"]
_require(row["split"] == row["source_split"] == "training",
"Only named training CS controls may alias streams")
_require(len(key) == len(source) == 6 and key[2] == source[2] == "CS",
"CS/US/design/evaluation role aliasing is forbidden")
_require(key[:5] == source[:5], "Alias crosses block/replicate/assay/cue/occurrence")
if key[1] in ("normal", "delayed"):
_require(source[5] == "paired" and key[5] in CONTROL_ALIASES,
"Unknown normal/delayed control alias")
else:
_require(key[1] == "reversal" and source[5] == "reversal" and key[5] == "no_reversal",
"Unknown reversal control alias")
def validate_trial_plan(payload):
"""Fail closed on incomplete coverage, illegal aliasing, mutation or types.
Rebuild the whole fixed 40-block schedule and every RNG derivation. This
does not trust a count, a caller's declared sha, or a row labeled replicate.
Canonical comparison distinguishes Boolean/integer/floating-point JSON.
"""
_require(type(payload) is dict and set(payload) == {"plan", "seed_manifest", "budget", "payload_sha256"},
"Invalid complete trial-plan envelope")
actual = {key: value for key, value in payload.items() if key != "payload_sha256"}
_require(type(payload["payload_sha256"]) is str and payload["payload_sha256"] == _sha(actual),
"Actual trial-plan payload SHA mismatch")
seed_registry.validate_manifest(payload["seed_manifest"])
_validate_alias_permissions(payload["seed_manifest"])
_require(payload["plan"]["master"] == payload["seed_manifest"]["master"], "Master identities differ")
expected = _assemble(payload["plan"]["master"])
_require(_canonical(payload) == _canonical(expected),
"Full protocol coverage/input binding/order/type reconstruction mismatch")
return True
def build_trial_plan(master):
"""Return a deterministic JSON-serializable DRAFT inventory without I/O."""
return _assemble(master)