#!/usr/bin/env python3 """SRP Exp002 state-table synthetics — PROPOSAL v0.3.5-draft (corrected v0.3.4). Responds to ChatGPT/Astra e413b069 CHANGES_REQUESTED (two independent negative tests fail). PROPOSAL ONLY · MODEL_CHANGE=NO · insufficiency preserved · no empirical rerun · NOT v0.4. Fixes vs v0.3.4: 1. CONFIRMED_UNRECOVERED: evaluate R_days_max / R_followup_max_polls BEFORE trough/cross updates. Boundary: (day - confirm_day) > R_days_max → CENSORED_NO_CROSS (541d after confirm censored under 540). 2. T_recovery_recognized set ONLY when BOTH T_cross_obs and T_confirm exist (EOS unrecovered → recognition NULL). 3. case_I c5 tautology removed; asserts ep1 closed as RELAPSED with reason refractory_restart_new_onset. 4. refractory_polls: ONLY value 1 supported (assert); remaining=0 after close so next poll is eligible. 5. New cases J (541d day-limit before cross) and K (EOS unrecovered recognition NULL). """ from __future__ import annotations import hashlib import json from copy import deepcopy from dataclasses import dataclass, field, asdict from datetime import datetime, timezone from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Tuple VERSION = "v0.3.5-draft" KIND = "STATE_TABLE_SYNTHETIC_TESTS_V0_3_5" OUT_DIR = Path("/workspace/srp-observatory/data/snapshots/gates_20260909/exp002_state_table_v035") HUB_DIR = Path("/workspace/multi-ai-hub/exp002_v035") OBS_DOC = Path("/workspace/srp-observatory/docs/experiments/SRP_EXPERIMENT_002_STATE_TABLE_V0_3_5.md") WORKER_DOCS = Path("/workspace/workers/ai-hub/src/docs") # --------------------------------------------------------------------------- # Default params (proposal) # --------------------------------------------------------------------------- DEFAULTS = { "B0": 40.0, "D": 8.0, "K": 2, "N_confirm_window": 3, # K of next N post-onset polls "confirm_days": 540, "M": 8, "trough_policy": "revise_on_deeper_trough", "baseline_rule": "STRICTLY_TRAILING_M", "confirm_rule": "K_OF_NEXT_N_WITHIN_CONFIRM_DAYS_OR_IMMEDIATE_NEXT", "confirm_hits_exclude_onset": True, "refractory_polls": 1, # next onset search starts at poll AFTER close "R_followup_max_polls": 6, # post-confirm polls allowed while CONFIRMED_UNRECOVERED "R_days_max": 540, # calendar days after T_confirm for recovery follow-up } def r50_of(B0: float, xmin: float) -> float: return B0 - 0.5 * (B0 - xmin) # == xmin + 0.5*(B0-xmin) def sha256_bytes(b: bytes) -> str: return hashlib.sha256(b).hexdigest() def sha256_text(s: str) -> str: return sha256_bytes(s.encode("utf-8")) def sha256_json(obj: Any) -> str: return sha256_text(json.dumps(obj, sort_keys=True, separators=(",", ":"))) @dataclass class Episode: onset_t: Optional[int] = None onset_day: Optional[int] = None B0: Optional[float] = None xmin: Optional[float] = None xmin_t: Optional[int] = None r50: Optional[float] = None confirm_hit_indices: List[int] = field(default_factory=list) # post-onset ordinals 1..N confirm_hit_ts: List[int] = field(default_factory=list) post_onset_count: int = 0 T_cross_obs: Optional[int] = None cross_interval: Optional[List[int]] = None last_below_t: Optional[int] = None T_confirm: Optional[int] = None T_recovery_recognized: Optional[int] = None x_at_confirm: Optional[float] = None same_poll_cross_confirm: bool = False crossings_cleared_preconfirm: int = 0 label: Optional[str] = None reason: Optional[str] = None cross_preceded_confirm: Optional[bool] = None episode_id: int = 0 closed: bool = False unrecovered_followup_polls: int = 0 baseline_source_ts: Optional[List[int]] = None # for audit of recompute def snapshot(self) -> Dict[str, Any]: d = asdict(self) d["B1_audit"] = { "anchor": "T_cross_obs", "estimand": "crossing_anchored_retrospective_audit", "status": "VISIBLE_BUT_NOT_COMPUTED_IN_THIS_PACKET", "applies_even_if_historical": True, } return d class StateMachine: """Forward-only episode machine — v0.3.4-draft semantics.""" def __init__(self, params: Optional[Dict[str, Any]] = None, history: Optional[List[Tuple[int, float, int]]] = None): # history items: (t, x, day) for baseline recompute; day defaults to t if omitted self.p = {**DEFAULTS, **(params or {})} self.state = "SEEK_ONSET" self.ep: Optional[Episode] = None self.events: List[Dict[str, Any]] = [] self.closed_episodes: List[Episode] = [] self.history: List[Tuple[int, float, int]] = list(history or []) self.episode_counter = 0 self.refractory_remaining = 0 # polls to skip before SEEK can fire self._last_t: Optional[int] = None def _onset_thr(self, B0: float) -> float: return B0 - self.p["D"] def _confirm_thr(self, B0: float) -> float: return B0 - self.p["D"] / 2.0 def _compute_baseline(self, before_t: int) -> Tuple[Optional[float], Optional[List[int]]]: """STRICTLY_TRAILING_M: median of exactly M polls with t < before_t.""" M = int(self.p["M"]) prior = [(t, x, d) for (t, x, d) in self.history if t < before_t] if len(prior) < M: return None, None window = prior[-M:] vals = sorted(x for (_, x, _) in window) n = len(vals) if n % 2 == 1: med = vals[n // 2] else: med = 0.5 * (vals[n // 2 - 1] + vals[n // 2]) return float(med), [t for (t, _, _) in window] def _emit(self, kind: str, **kw: Any) -> None: self.events.append({"kind": kind, **kw}) def _close_episode(self, label: str, reason: str, t: int, x: Optional[float] = None) -> None: assert self.ep is not None # v0.3.5: only refractory_polls==1 is supported (emitted value must match implemented behavior). rp = int(self.p["refractory_polls"]) if rp != 1: raise ValueError( f"unsupported refractory_polls={rp}; v0.3.5 supports only 1 " "(do not imply tested configurability for other values)" ) self.ep.label = label self.ep.reason = reason self.ep.closed = True if self.ep.T_cross_obs is not None and self.ep.T_confirm is not None: self.ep.cross_preceded_confirm = self.ep.T_cross_obs < self.ep.T_confirm # recognition requires BOTH events self.ep.T_recovery_recognized = max(self.ep.T_cross_obs, self.ep.T_confirm) else: # confirm-without-cross (or cross-without-confirm): recognition stays NULL if self.ep.T_confirm is not None and self.ep.T_cross_obs is None: self.ep.cross_preceded_confirm = False self.ep.T_recovery_recognized = None self.closed_episodes.append(deepcopy(self.ep)) self._emit("EPISODE_CLOSED", t=t, label=label, reason=reason, episode_id=self.ep.episode_id) self.ep = None # refractory_polls=1 ⇒ next distinct poll may onset (closing poll already consumed). self.refractory_remaining = 0 self.state = "SEEK_ONSET" def _maybe_record_cross(self, t: int, x: float) -> bool: """Record T_cross_obs in PENDING or CONFIRMED_UNRECOVERED if x >= r50 and trough established.""" assert self.ep is not None and self.ep.r50 is not None if self.ep.T_cross_obs is not None: return False if self.ep.xmin_t is None or t <= self.ep.xmin_t: # recovery cannot be declared on the same poll that creates the trough return False if x >= self.ep.r50: last_below = self.ep.last_below_t if self.ep.last_below_t is not None else self.ep.xmin_t self.ep.T_cross_obs = t self.ep.cross_interval = [last_below, t] self._emit( "CROSS_OBS_RECORDED", t=t, x=x, r50=self.ep.r50, interval=list(self.ep.cross_interval), state=self.state, ) return True return False def _deeper_trough(self, t: int, x: float) -> None: assert self.ep is not None if x < (self.ep.xmin if self.ep.xmin is not None else float("inf")): cleared = self.ep.T_cross_obs is not None if cleared and self.p["trough_policy"] == "revise_on_deeper_trough": self.ep.crossings_cleared_preconfirm += 1 self.ep.T_cross_obs = None self.ep.cross_interval = None self.ep.xmin = x self.ep.xmin_t = t self.ep.r50 = r50_of(self.ep.B0, x) self.ep.last_below_t = t self._emit( "DEEPER_TROUGH_REVISE", t=t, x=x, xmin=self.ep.xmin, r50=self.ep.r50, cleared_prior_crossing=cleared, policy=self.p["trough_policy"], ) def _try_confirm(self, t: int, x: float, day: int) -> bool: """Apply primary confirmation rule. Returns True if confirmation decided this poll.""" assert self.ep is not None B0 = self.ep.B0 assert B0 is not None onset_thr = self._onset_thr(B0) confirm_thr = self._confirm_thr(B0) N = int(self.p["N_confirm_window"]) K = int(self.p["K"]) confirm_days = int(self.p["confirm_days"]) # calendar timeout if self.ep.onset_day is not None and (day - self.ep.onset_day) > confirm_days: self._emit( "CONFIRM_WINDOW_TIMEOUT", t=t, day=day, onset_day=self.ep.onset_day, confirm_days=confirm_days, hits=len(self.ep.confirm_hit_indices), ) self._close_episode("CENSORED", "confirm_window_timeout_540d", t, x) return True # post-onset ordinal (onset itself excluded) self.ep.post_onset_count += 1 ordinal = self.ep.post_onset_count # 1 = first post-onset poll # immediate-next rule: first post-onset poll ≤ B0-D immediate_confirm = ordinal == 1 and x <= onset_thr # K-of-next-N: only polls with ordinal <= N count in_window = ordinal <= N is_hit = x <= confirm_thr if in_window and is_hit: self.ep.confirm_hit_indices.append(ordinal) self.ep.confirm_hit_ts.append(t) self._emit( "CONFIRM_HIT", t=t, x=x, ordinal=ordinal, hits=len(self.ep.confirm_hit_indices), need=K, window_N=N, in_window=True, ) elif is_hit and not in_window: self._emit( "CONFIRM_HIT_OUTSIDE_WINDOW_IGNORED", t=t, x=x, ordinal=ordinal, window_N=N, note="v0.3.4 primary rule does NOT append beyond next-N; v0.3.3 silent broadening rejected", ) decided = False if immediate_confirm: decided = True self._emit("CONFIRM_IMMEDIATE_NEXT", t=t, x=x, thr=onset_thr) elif len(self.ep.confirm_hit_indices) >= K: decided = True elif ordinal >= N: # window exhausted without K self._emit( "CONFIRM_WINDOW_EXHAUSTED", t=t, ordinal=ordinal, hits=len(self.ep.confirm_hit_indices), need=K, window_N=N, ) self._close_episode("CENSORED", "confirm_window_exhausted_K_of_next_N", t, x) return True if not decided: return False # Confirmation decidable now self.ep.T_confirm = t self.ep.x_at_confirm = x crossed_this = False # Same-poll: try cross before labeling (order: confirm bookkeeping already done; cross next) if self.ep.T_cross_obs is None: crossed_this = self._maybe_record_cross(t, x) if crossed_this and self.ep.T_cross_obs == t: self.ep.same_poll_cross_confirm = True self._emit("SAME_POLL_CROSS_AND_CONFIRM", t=t, x=x, r50=self.ep.r50) if self.ep.T_cross_obs is not None: # label by x_at_confirm vs r50 — NOT by whether cross preceded confirm if x >= self.ep.r50: label = "RECOVERED_CURRENT" else: label = "RECOVERED_HISTORICAL" self.ep.cross_preceded_confirm = self.ep.T_cross_obs < t self.ep.T_recovery_recognized = max(self.ep.T_cross_obs, t) self._emit( "CONFIRM_DECIDABLE", t=t, label=label, T_cross_obs=self.ep.T_cross_obs, cross_interval=self.ep.cross_interval, T_confirm=t, T_recovery_recognized=self.ep.T_recovery_recognized, xmin=self.ep.xmin, xmin_t=self.ep.xmin_t, r50=self.ep.r50, B0=self.ep.B0, onset_t=self.ep.onset_t, x_at_confirm=x, same_poll_cross_confirm=self.ep.same_poll_cross_confirm, crossings_cleared_preconfirm=self.ep.crossings_cleared_preconfirm, cross_preceded_confirm=self.ep.cross_preceded_confirm, definition="CURRENT_IFF_x_at_confirm_ge_r50__NOT_crossing_before_confirm", ) self.state = label # Episode stays open for relapse tracking if CURRENT; HISTORICAL already below if label == "RECOVERED_CURRENT": pass # stay in RECOVERED_CURRENT else: pass # RECOVERED_HISTORICAL self.ep.label = label return True else: # Confirmed but no crossing yet → CONFIRMED_UNRECOVERED (NOT CENSORED_NO_CROSS) self._emit( "ENTER_CONFIRMED_UNRECOVERED", t=t, x=x, r50=self.ep.r50, note="confirmation-first; recovery may arrive later within R_followup_max / R_days_max", ) self.state = "CONFIRMED_UNRECOVERED" self.ep.label = "CONFIRMED_UNRECOVERED" self.ep.T_confirm = t self.ep.x_at_confirm = x self.ep.unrecovered_followup_polls = 0 return True def _handle_unrecovered(self, t: int, x: float, day: int) -> None: assert self.ep is not None self.ep.unrecovered_followup_polls += 1 # v0.3.5: evaluate follow-up eligibility BEFORE trough/cross updates. # Boundary inclusivity: censor when (day - confirm_day) > R_days_max # (e.g. confirm day 802, R=540 → day 1343 is 541d later → CENSORED_NO_CROSS). if self.ep.unrecovered_followup_polls > int(self.p["R_followup_max_polls"]): # keep prior H semantics: limit at >= max after increment; see note below pass confirm_day = None if self.ep.T_confirm is not None: for (ht, hx, hd) in self.history: if ht == self.ep.T_confirm: confirm_day = hd break day_expired = ( confirm_day is not None and (day - confirm_day) > int(self.p["R_days_max"]) ) poll_expired = self.ep.unrecovered_followup_polls >= int(self.p["R_followup_max_polls"]) # Day limit checked first and before any cross — crossing past the horizon does not recover. if day_expired: self._close_episode("CENSORED_NO_CROSS", "unrecovered_day_limit", t, x) return if poll_expired: # Still allow a cross on the limit poll itself? Prior H closes on 6th follow-up without requiring # pre-cross. Keep: on the limit-reaching poll, check cross first only if NOT day-expired. # Astra day-limit case is the governing new rule; poll limit retains prior behavior after # eligibility when still inside day horizon: try cross, else close. pass # deeper trough still updates r50 while unrecovered (only if still eligible) if x < (self.ep.xmin if self.ep.xmin is not None else float("inf")): self._deeper_trough(t, x) if x < (self.ep.r50 or 0): self.ep.last_below_t = t crossed = self._maybe_record_cross(t, x) if crossed: # recovery-later → RECOVERED_CURRENT (x >= r50 by definition of cross) self.ep.T_recovery_recognized = max(self.ep.T_cross_obs, self.ep.T_confirm) self.ep.cross_preceded_confirm = self.ep.T_cross_obs < self.ep.T_confirm self.ep.label = "RECOVERED_CURRENT" self.state = "RECOVERED_CURRENT" self._emit( "RECOVERY_AFTER_CONFIRM", t=t, x=x, T_cross_obs=self.ep.T_cross_obs, T_confirm=self.ep.T_confirm, T_recovery_recognized=self.ep.T_recovery_recognized, label="RECOVERED_CURRENT", ) return if poll_expired: self._close_episode("CENSORED_NO_CROSS", "unrecovered_followup_poll_limit", t, x) return def ingest(self, t: int, x: float, day: Optional[int] = None) -> None: if day is None: day = t # synthetic: treat t as day index unless overridden if self._last_t is not None and t < self._last_t: raise ValueError("time must be non-decreasing") self._last_t = t # Per-arrival order: ingest → candidate/confirm → trough → crossing → state → recognition # Append to history AFTER processing onset eligibility using prior history only. prior_history_len = len(self.history) if self.state == "SEEK_ONSET": B0, src = self._compute_baseline(t) if B0 is not None and x <= self._onset_thr(B0): self.episode_counter += 1 self.ep = Episode( onset_t=t, onset_day=day, B0=B0, xmin=x, xmin_t=t, r50=r50_of(B0, x), last_below_t=t, episode_id=self.episode_counter, baseline_source_ts=src, ) self.state = "PENDING" self._emit( "ENTER_PENDING", t=t, x=x, xmin=x, r50=self.ep.r50, B0=B0, baseline_source_ts=src, episode_id=self.episode_counter, ) self.history.append((t, x, day)) return if self.state == "PENDING": assert self.ep is not None # confirmation bookkeeping first (uses this poll as post-onset) # but trough revise on this poll should happen before cross; confirm hits use raw x # Order per table: candidate update → confirmation → trough → crossing → state # For deeper trough on same poll as confirm hit, trough revise clears cross then hit counts. # Implement: trough first if deeper, then confirm, then cross (if not cleared). if x < (self.ep.xmin if self.ep.xmin is not None else float("inf")): self._deeper_trough(t, x) elif x < (self.ep.r50 or 0): self.ep.last_below_t = t decided = self._try_confirm(t, x, day) if self.state == "PENDING" and not decided: # try cross while still pending self._maybe_record_cross(t, x) elif self.state in ("RECOVERED_CURRENT", "RECOVERED_HISTORICAL"): pass elif self.state == "CONFIRMED_UNRECOVERED": pass elif self.state == "SEEK_ONSET": # closed via window exhaust/timeout self.history.append((t, x, day)) return self.history.append((t, x, day)) return if self.state == "CONFIRMED_UNRECOVERED": self._handle_unrecovered(t, x, day) self.history.append((t, x, day)) return if self.state == "RECOVERED_CURRENT": assert self.ep is not None if x < (self.ep.r50 or 0): self.state = "RELAPSED" self.ep.label = "RELAPSED" self._emit("RELAPSE", t=t, x=x, r50=self.ep.r50) self.history.append((t, x, day)) return if self.state in ("RECOVERED_HISTORICAL", "RELAPSED"): assert self.ep is not None # Refractory restart: new onset-sized drop closes prior and starts new PENDING # Baseline recomputed from history BEFORE this poll B0, src = self._compute_baseline(t) if B0 is not None and x <= self._onset_thr(B0): # close prior episode self._close_episode(self.state, "refractory_restart_new_onset", t, x) # start new self.episode_counter += 1 self.ep = Episode( onset_t=t, onset_day=day, B0=B0, xmin=x, xmin_t=t, r50=r50_of(B0, x), last_below_t=t, episode_id=self.episode_counter, baseline_source_ts=src, ) self.state = "PENDING" self._emit( "REFRACTORY_RESTART_ENTER_PENDING", t=t, x=x, B0=B0, r50=self.ep.r50, baseline_source_ts=src, episode_id=self.episode_counter, refractory_polls=self.p["refractory_polls"], note="baseline STRICTLY_TRAILING_M recomputed at new onset", ) self.history.append((t, x, day)) return # CENSORED paths already returned to SEEK_ONSET self.history.append((t, x, day)) def end_of_series(self) -> None: if self.state == "PENDING" and self.ep is not None: self._emit( "CENSORED_END_OF_SERIES", t=self._last_t, pending_onset=self.ep.onset_t, had_cross=self.ep.T_cross_obs is not None, confirm_hits=len(self.ep.confirm_hit_indices), ) self._close_episode("CENSORED", "end_of_series_before_K_confirm", self._last_t or -1) elif self.state == "CONFIRMED_UNRECOVERED" and self.ep is not None: self._close_episode("CENSORED_NO_CROSS", "end_of_series_while_unrecovered", self._last_t or -1) elif self.state in ("RECOVERED_CURRENT", "RECOVERED_HISTORICAL", "RELAPSED") and self.ep is not None: self._close_episode(self.state, "end_of_series_after_confirm", self._last_t or -1) def final_state(self) -> str: return self.state if self.ep is None or self.ep.closed else self.state # --------------------------------------------------------------------------- # Synthetic cases — full inputs # --------------------------------------------------------------------------- def warm_history(B0_target: float = 40.0, M: int = 8) -> List[Tuple[int, float, int]]: """M polls all at B0_target so baseline median = B0_target.""" return [(700 + i, B0_target, 700 + i) for i in range(M)] CASES: Dict[str, Dict[str, Any]] = {} def _run(name: str, polls: List[Tuple[int, float]], checks: List[Callable], meta: Dict[str, Any], days: Optional[Dict[int, int]] = None, end: bool = True, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: hist = warm_history() sm = StateMachine(params=params, history=hist) full_inputs = { "warm_history": [{"t": t, "x": x, "day": d} for t, x, d in hist], "polls": [{"t": t, "x": x, "day": (days or {}).get(t, t)} for t, x in polls], "params": {**DEFAULTS, **(params or {})}, "end_of_series": end, } for t, x in polls: sm.ingest(t, x, day=(days or {}).get(t, t)) if end: sm.end_of_series() episode = None if sm.closed_episodes: episode = sm.closed_episodes[-1].snapshot() elif sm.ep is not None: episode = sm.ep.snapshot() result = { "final_state": sm.state if sm.ep is None else sm.state, "episode": episode, "events": sm.events, "n_closed_episodes": len(sm.closed_episodes), "closed_episode_labels": [e.label for e in sm.closed_episodes], "full_inputs": full_inputs, "full_inputs_sha256": sha256_json(full_inputs), } check_results = [] for fn in checks: ok, detail = fn(sm, result) check_results.append({"pass": bool(ok), "detail": detail}) result["checks"] = check_results result["pass"] = all(c["pass"] for c in check_results) result["meta"] = meta return result # Case A: cross then confirm CURRENT (x_at_confirm >= r50) despite earlier crossing # Demonstrates alignment: CURRENT because x>=r50 at confirm, NOT despite cross-before-confirm def case_A(): polls = [(800, 24.0), (802, 38.0), (803, 35.0), (804, 35.0)] # post-onset: 802(no hit 38>36), 803(hit), 804(hit) → K=2 of next3 within window def c1(sm, r): return r["episode"]["label"] == "RECOVERED_CURRENT", "label_CURRENT" def c2(sm, r): return r["episode"]["T_cross_obs"] == 802, "T_cross=802" def c3(sm, r): return r["episode"]["T_confirm"] == 804, "T_confirm=804" def c4(sm, r): return r["episode"]["x_at_confirm"] >= r["episode"]["r50"], "x_ge_r50" def c5(sm, r): return r["episode"]["cross_preceded_confirm"] is True, "cross_preceded_confirm_audit" def c6(sm, r): # Explicit: would be mislabeled HISTORICAL under Claude 7a7c35f8 'crossing before confirm' return r["episode"]["label"] == "RECOVERED_CURRENT", "not_historical_despite_early_cross" return _run( "A_cross_then_confirm_current", polls, [c1, c2, c3, c4, c5, c6], {"codex_item": "cross then confirm, current; aligns HISTORICAL/CURRENT vs Claude 7a7c35f8"}, ) def case_B(): # cross then relapse below r50 by confirm → HISTORICAL polls = [(800, 17.0), (802, 38.0), (803, 26.0), (804, 20.0)] # r50 = 40 - 0.5*(40-17)=28.5; hits: 802 no, 803 yes (26<=36), 804 yes (20<=36) def c1(sm, r): return r["episode"]["label"] == "RECOVERED_HISTORICAL", "label_HISTORICAL" def c2(sm, r): return r["episode"]["x_at_confirm"] < r["episode"]["r50"], "below_r50" def c3(sm, r): return r["episode"]["T_cross_obs"] == 802 and r["episode"]["T_confirm"] == 804, "times" return _run( "B_cross_then_relapse_historical", polls, [c1, c2, c3], {"codex_item": "cross then relapse before confirm → HISTORICAL by x_at_confirm < r50"}, ) def case_C(): """Case C under RESTORED K-of-next-3: must NOT confirm. Post-onset polls: 802,803,804 = next 3. Only 803 qualifies (x=20<=36). 805 is 4th post-onset — outside window. v0.3.3 wrongly confirmed at 805. """ polls = [(800, 24.0), (802, 38.0), (803, 20.0), (804, 38.0), (805, 34.0)] def c1(sm, r): return r["episode"]["label"] == "CENSORED", "censored_not_confirmed" def c2(sm, r): return r["episode"]["reason"] == "confirm_window_exhausted_K_of_next_N", "reason_window" def c3(sm, r): # only one hit inside the next-3 window; episode closes at 3rd post-onset (804) # so poll 805 never enters confirm bookkeeping (SEEK_ONSET, x=34 > onset_thr) hits_in = [e for e in r["events"] if e["kind"] == "CONFIRM_HIT"] return len(hits_in) == 1 and hits_in[0]["t"] == 803, "one_hit_in_window_at_803" def c4(sm, r): return any(e["kind"] == "CONFIRM_WINDOW_EXHAUSTED" for e in r["events"]), "exhausted_event" def c5(sm, r): return r["episode"].get("T_confirm") is None, "no_T_confirm" return _run( "C_deeper_trough_K_of_next_3_does_not_confirm", polls, [c1, c2, c3, c4, c5], { "codex_item": "Case C: restore K-of-next-3; v0.3.3 silent append rejected", "v033_bug": "v0.3.3 confirmed at 805 after 4 post-onset polls; only 803 of first 3 qualifies", }, ) def case_C_contrast_unbounded(): """Named non-default variant showing what unbounded append would do (NOT primary).""" polls = [(800, 24.0), (802, 38.0), (803, 20.0), (804, 38.0), (805, 34.0)] # Simulate unbounded by setting N_confirm_window very large — DECLARED VARIANT only params = {"N_confirm_window": 99, "confirm_rule": "VARIANT_UNBOUNDED_APPEND_UNTIL_K_NOT_DEFAULT"} def c1(sm, r): return r["episode"]["label"] == "RECOVERED_CURRENT", "variant_would_confirm" def c2(sm, r): return r["episode"]["T_confirm"] == 805, "confirm_at_805" def c3(sm, r): return r["full_inputs"]["params"]["confirm_rule"].startswith("VARIANT_"), "labeled_variant" return _run( "C_contrast_unbounded_append_VARIANT_NOT_DEFAULT", polls, [c1, c2, c3], { "codex_item": "explicit contrast: unbounded append WOULD confirm at 805 — NOT the primary rule", "primary": False, }, params=params, ) def case_D(): # Onset at B0-D so r50=36; allows hit-without-cross band (32,36) and avoids accidental immediate-next. # v0.3.3 used onset=24,x=30 which is ≤B0-D and would immediate-confirm under frozen §4.2 — restored here. polls = [(800, 32.0), (802, 34.0), (804, 36.0)] # r50=36; 802 hit (34<=36) no cross (34<36); 804 hit2 + cross (36>=36) same-poll def c1(sm, r): return r["episode"]["same_poll_cross_confirm"] is True, "same" def c2(sm, r): return r["episode"]["T_cross_obs"] == 804 and r["episode"]["T_confirm"] == 804, "times" def c3(sm, r): return r["episode"]["label"] == "RECOVERED_CURRENT", "label" def c4(sm, r): return not any(e["kind"] == "CONFIRM_IMMEDIATE_NEXT" for e in r["events"]), "not_immediate" return _run( "D_same_poll_cross_and_confirm", polls, [c1, c2, c3, c4], {"codex_item": "same-poll confirm/cross; onset at B0-D so immediate-next does not fire early"}, ) def case_E_eos(): polls = [(800, 24.0), (802, 38.0), (803, 35.0)] # only 1 hit, then EOS def c1(sm, r): return r["episode"]["label"] == "CENSORED", "censored" def c2(sm, r): return r["episode"]["reason"] == "end_of_series_before_K_confirm", "eos_reason" def c3(sm, r): return r["episode"].get("T_confirm") is None, "no_conf" return _run( "E_end_of_series_censored", polls, [c1, c2, c3], {"codex_item": "EOS before K — NOT a timeout test"}, ) def case_E2_timeout(): """Explicit 540-day confirm timeout (distinct from EOS).""" # onset day 800; polls within next-3 but calendar exceeds 540 before K polls = [(800, 24.0), (802, 38.0), (803, 35.0)] days = {800: 1000, 802: 1200, 803: 1600} # 1600-1000=600 > 540 def c1(sm, r): return r["episode"]["label"] == "CENSORED", "censored" def c2(sm, r): return r["episode"]["reason"] == "confirm_window_timeout_540d", "timeout_reason" def c3(sm, r): return any(e["kind"] == "CONFIRM_WINDOW_TIMEOUT" for e in r["events"]), "timeout_event" return _run( "E2_confirm_window_timeout_540d", polls, [c1, c2, c3], {"codex_item": "540d timeout ≠ EOS"}, days=days, ) def case_F(): polls = [(800, 24.0), (801, 38.0), (802, 35.0), (803, 35.0)] def c1(sm, r): kinds = [e["kind"] for e in r["events"]] return kinds.index("CROSS_OBS_RECORDED") < kinds.index("CONFIRM_DECIDABLE"), "order" def c2(sm, r): return not any(e["kind"].startswith("RECOVERY_EMIT") for e in r["events"]), "no_rec_preconfirm" def c3(sm, r): return r["episode"]["label"] == "RECOVERED_CURRENT", "label" return _run( "F_pending_tracks_cross_before_confirm", polls, [c1, c2, c3], {"codex_item": "PENDING tracks cross before confirm"}, ) def case_G_confirm_first_recovery_later(): """Confirmation without cross → CONFIRMED_UNRECOVERED → later recovery.""" # Onset at B0-D=32 → r50=36. Hit band without cross: 32 < x < 36. # Avoids immediate-next (requires x≤32 on first post-onset). polls = [ (800, 32.0), # onset (801, 34.0), # hit1, below r50 (802, 34.0), # hit2 → confirm, still no cross → CONFIRMED_UNRECOVERED (803, 33.0), # still below (804, 37.0), # cross >=36 → RECOVERED_CURRENT ] def c1(sm, r): return any(e["kind"] == "ENTER_CONFIRMED_UNRECOVERED" for e in r["events"]), "entered_unrecovered" def c2(sm, r): return any(e["kind"] == "RECOVERY_AFTER_CONFIRM" for e in r["events"]), "recovery_later" def c3(sm, r): return r["episode"]["label"] == "RECOVERED_CURRENT", "final_current" def c4(sm, r): return r["episode"]["T_confirm"] == 802 and r["episode"]["T_cross_obs"] == 804, "times_confirm_then_cross" def c5(sm, r): return not any( e["kind"] == "CONFIRM_DECIDABLE" and e.get("label") == "CENSORED_NO_CROSS" for e in r["events"] ), "not_immediate_censored_no_cross" return _run( "G_confirm_first_recovery_later", polls, [c1, c2, c3, c4, c5], {"codex_item": "CONFIRMED_UNRECOVERED then recovery-later; not immediate CENSORED_NO_CROSS"}, ) def case_H_unrecovered_limit(): polls = [ (800, 24.0), (801, 30.0), (802, 30.0), # confirm unrecovered (803, 28.0), (804, 28.0), (805, 28.0), (806, 28.0), (807, 28.0), (808, 28.0), # 6th follow-up → limit ] params = {"R_followup_max_polls": 6} def c1(sm, r): return r["episode"]["label"] == "CENSORED_NO_CROSS", "censored_no_cross" def c2(sm, r): return r["episode"]["reason"] == "unrecovered_followup_poll_limit", "limit_reason" def c3(sm, r): return any(e["kind"] == "ENTER_CONFIRMED_UNRECOVERED" for e in r["events"]), "was_unrecovered" return _run( "H_confirmed_unrecovered_then_limit", polls, [c1, c2, c3], {"codex_item": "CENSORED_NO_CROSS only after follow-up limit, not at confirm"}, params=params, ) def case_I_refractory_multi_episode(): """Two episodes: first recovers CURRENT, relapses, then refractory restart with baseline recompute.""" # Build enough history so second onset gets a different B0 if values change. # Episode1: onset 800 x=24 (B0=40), confirm with cross current, then relapse, then new deep drop. polls = [ (800, 24.0), (801, 30.0), # hit1 (802, 34.0), # hit2 + cross → RECOVERED_CURRENT (803, 28.0), # relapse (28 < r50=32) (804, 40.0), # high poll enters history (affects later baseline) (805, 40.0), (806, 40.0), (807, 40.0), (808, 40.0), (809, 40.0), (810, 40.0), (811, 40.0), # now trailing 8 can be mostly 40s (812, 24.0), # new onset — baseline recomputed (813, 30.0), (814, 34.0), # confirm+cross episode 2 ] def c1(sm, r): return r["n_closed_episodes"] >= 1 and r["episode"]["episode_id"] == 2, "two_episodes" def c2(sm, r): return any(e["kind"] == "REFRACTORY_RESTART_ENTER_PENDING" for e in r["events"]), "restart_event" def c3(sm, r): restarts = [e for e in r["events"] if e["kind"] == "REFRACTORY_RESTART_ENTER_PENDING"] return restarts and restarts[0].get("baseline_source_ts") is not None, "baseline_recomputed" def c4(sm, r): return r["episode"]["label"] == "RECOVERED_CURRENT", "ep2_current" def c5(sm, r): ep1 = sm.closed_episodes[0] return ( ep1.label == "RELAPSED" and ep1.reason == "refractory_restart_new_onset" and ep1.T_recovery_recognized is not None ), "ep1_closed_relapsed_with_recognition" def c6(sm, r): # refractory_polls explicit in event restarts = [e for e in r["events"] if e["kind"] == "REFRACTORY_RESTART_ENTER_PENDING"] return restarts and restarts[0].get("refractory_polls") == 1, "refractory_duration_1" return _run( "I_refractory_restart_multi_episode", polls, [c1, c2, c3, c4, c5, c6], { "codex_item": "refractory duration=1 poll; baseline STRICTLY_TRAILING_M recomputed; multi-episode", }, ) def case_J_day_limit_before_cross(): """Astra negative: confirm then cross 541d later under R_days_max=540 → CENSORED_NO_CROSS.""" polls = [(800, 32.0), (801, 34.0), (802, 34.0), (1343, 37.0)] def c1(sm, r): return r["episode"]["label"] == "CENSORED_NO_CROSS", "censored_no_cross" def c2(sm, r): return r["episode"]["reason"] == "unrecovered_day_limit", "day_limit_reason" def c3(sm, r): return r["episode"]["T_confirm"] == 802 and r["episode"]["T_cross_obs"] is None, "no_cross_recorded" def c4(sm, r): return any(e["kind"] == "ENTER_CONFIRMED_UNRECOVERED" for e in r["events"]), "was_unrecovered" return _run( "J_recovery_day_limit_before_cross", polls, [c1, c2, c3, c4], {"astra_item": "541d after confirm under R_days_max=540 → CENSORED_NO_CROSS; eligibility before cross"}, ) def case_K_eos_unrecovered_recognition_null(): """Astra negative: confirm without cross then EOS → recognition must stay NULL.""" polls = [(800, 32.0), (801, 34.0), (802, 34.0)] def c1(sm, r): return r["episode"]["label"] == "CENSORED_NO_CROSS", "censored_no_cross" def c2(sm, r): return r["episode"]["T_cross_obs"] is None, "no_cross" def c3(sm, r): return r["episode"]["T_confirm"] == 802, "confirm_802" def c4(sm, r): return r["episode"]["T_recovery_recognized"] is None, "recognition_null" return _run( "K_eos_unrecovered_recognition_null", polls, [c1, c2, c3, c4], {"astra_item": "EOS unrecovered: T_recovery_recognized NULL until BOTH cross and confirm exist"}, ) def main() -> Dict[str, Any]: builders = [ ("A_cross_then_confirm_current", case_A), ("B_cross_then_confirm_historical", case_B), ("C_confirm_window_exhausted_K_of_next_N", case_C), ("C_contrast_unbounded_append_variant", case_C_contrast_unbounded), ("D_same_poll_cross_confirm", case_D), ("E_end_of_series", case_E_eos), ("E2_confirm_window_timeout_540d", case_E2_timeout), ("F_pending_tracks_cross_before_confirm", case_F), ("G_confirm_first_recovery_later", case_G_confirm_first_recovery_later), ("H_confirmed_unrecovered_then_limit", case_H_unrecovered_limit), ("I_refractory_restart_multi_episode", case_I_refractory_multi_episode), ("J_recovery_day_limit_before_cross", case_J_day_limit_before_cross), ("K_eos_unrecovered_recognition_null", case_K_eos_unrecovered_recognition_null), ] cases = {} for name, fn in builders: cases[name] = fn() source_path = Path(__file__).resolve() source_text = source_path.read_text(encoding="utf-8") source_sha = sha256_text(source_text) payload = { "kind": KIND, "version": VERSION, "status": "PROPOSAL_ONLY", "label": "v0.3.4-draft (corrected v0.3.3) — NOT v0.4 acceptance", "model_change": False, "empirical": False, "inference_authorized": False, "insufficiency_preserved": True, "responds_to": { "chatgpt_changes_before_v04": "817d6907-9cb0-420f-9452-10e12daf3c89", "codex_state_table": "44ddfaaf-2d90-4b3f-8f42-b9b79d510176", "claude_review": "3c271dfe-c671-4cfb-a4d2-66b9570ab000", "claude_handoff_harness": "7a7c35f8-b543-4ad4-bb24-6d14f05a3f07", "chatgpt_b1": "dce6003c-d5a0-4ee9-8308-cae3c72972e7", "astra_insufficiency": "72626335", }, "fixes_vs_v033": [ "Restore K-of-next-3 + 540d confirmation boundary; reject silent unbounded append (Case C)", "CONFIRMED_UNRECOVERED on confirm-without-cross; CENSORED_NO_CROSS only after follow-up limit", "Explicit refractory_polls=1 + baseline recompute + multi-episode test; EOS ≠ timeout", "HISTORICAL/CURRENT = x_at_confirm ? r50; reject 7a7c35f8 'crossing-before-confirm=HISTORICAL'", "Runnable source + full_inputs + sha256 hashes for every case", ], "confirmation_rule_primary": { "name": "K_OF_NEXT_N_WITHIN_CONFIRM_DAYS_OR_IMMEDIATE_NEXT", "K": 2, "N": 3, "confirm_days": 540, "immediate_next": "first post-onset poll ≤ B0-D", "hits": "post-onset poll ≤ B0-D/2 counts only if ordinal ≤ N", "rejected_silent_broadening": "append_hits_until_K_without_window", "variant_unbounded": "VARIANT_UNBOUNDED_APPEND_UNTIL_K_NOT_DEFAULT (contrast only)", }, "historical_vs_current_definition": { "binding": "RECOVERED_CURRENT iff x_at_confirm ≥ r50; RECOVERED_HISTORICAL iff confirmed with prior T_cross_obs and x_at_confirm < r50", "rejected_from_claude_7a7c35f8": "Historical = crossing observed before confirmation date", "audit_flag": "cross_preceded_confirm (diagnostic only; does not set label)", "case_A": "CURRENT despite earlier crossing because x_at_confirm ≥ r50", }, "source": { "path": str(source_path), "sha256": source_sha, }, "cases": cases, "all_pass": all(c["pass"] for c in cases.values()), "generated_at": datetime.now(timezone.utc).isoformat(), } OUT_DIR.mkdir(parents=True, exist_ok=True) HUB_DIR.mkdir(parents=True, exist_ok=True) # Strip bulky events duplication for summary? Keep full — required. out_json = OUT_DIR / "state_table_synthetic_tests_v034.json" text = json.dumps(payload, indent=2, sort_keys=False) out_json.write_text(text, encoding="utf-8") payload_sha = sha256_text(text) # compact b1 lock b1 = { "kind": "B1_ESTIMAND_LOCK_V0_3_4", "version": VERSION, "status": "PROPOSAL_ONLY", "model_change": False, "insufficiency_preserved": True, "ratchet_computed": False, "inherits": "v0.3.3 B1 lock", "responds_to": payload["responds_to"], "decision": "PRESERVE_ALL_EPISODES_DEFINE_ESTIMAND_BEFORE_ANCHOR_CHANGE", "locked": [ "Keep crossing-anchored B1 as retrospective audit for ALL confirmed episodes (CURRENT and HISTORICAL)", "CONFIRMED_UNRECOVERED is confirmed-but-not-yet-recovered; B1 not anchored until T_cross_obs exists", "Missing/overlap/eligibility remain visible", "Recognition-anchored B1 = DECLARED_NOT_IMPLEMENTED", "Baseline STRICTLY_TRAILING_M (M=8; sensitivity {6,10}); recompute ALL baselines after lock", ], "B1_primary_audit_estimand": { "name": "B1_crossing_anchored_retrospective", "anchor": "T_cross_obs", "applies_to": ["RECOVERED_CURRENT", "RECOVERED_HISTORICAL"], "delta_B": "NOT computed in this packet", }, } b1_path = OUT_DIR / "b1_estimand_lock_v034.json" b1_path.write_text(json.dumps(b1, indent=2), encoding="utf-8") # copy source into snapshot src_copy = OUT_DIR / "run_exp002_state_table_v034_synthetics.py" src_copy.write_text(source_text, encoding="utf-8") # hashes manifest manifest = { "version": VERSION, "synthetic_tests_sha256": payload_sha, "synthetic_tests_path": str(out_json), "source_sha256": source_sha, "source_path": str(source_path), "b1_lock_sha256": sha256_text(b1_path.read_text(encoding="utf-8")), "case_input_hashes": {k: v["full_inputs_sha256"] for k, v in cases.items()}, "all_pass": payload["all_pass"], } man_path = OUT_DIR / "hashes_manifest_v034.json" man_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8") # hub copies (HUB_DIR / "state_table_synthetic_tests.json").write_text(text, encoding="utf-8") (HUB_DIR / "b1_estimand_lock.json").write_text(json.dumps(b1, indent=2), encoding="utf-8") (HUB_DIR / "hashes_manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8") (HUB_DIR / "run_exp002_state_table_v034_synthetics.py").write_text(source_text, encoding="utf-8") print(json.dumps({"all_pass": payload["all_pass"], "n_cases": len(cases), "case_pass": {k: v["pass"] for k, v in cases.items()}, "source_sha256": source_sha, "tests_sha256": payload_sha}, indent=2)) return payload if __name__ == "__main__": main()