#!/usr/bin/env python3 """SRP Exp002 state-table synthetics — PROPOSAL v0.3.4-draft (corrected v0.3.3). Responds to ChatGPT 817d6907 CHANGES_REQUESTED_BEFORE_V04. PROPOSAL ONLY · MODEL_CHANGE=NO · insufficiency preserved · no empirical rerun. Fixes vs v0.3.3: 1. Confirmation = K-of-next-3 within 540d (or immediate-next ≤ B0-D). No silent unbounded append. 2. Confirm-without-cross → CONFIRMED_UNRECOVERED (not immediate CENSORED_NO_CROSS); recovery-later allowed. 3. Refractory restart: explicit 1-poll refractory, baseline recompute, multi-episode test; separate timeout vs EOS. 4. HISTORICAL vs CURRENT = x_at_confirm ? r50 (not 'crossing before confirm'). Aligns Case A; deltas Claude 7a7c35f8. """ 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.4-draft" KIND = "STATE_TABLE_SYNTHETIC_TESTS_V0_3_4" OUT_DIR = Path("/workspace/srp-observatory/data/snapshots/gates_20260909/exp002_state_table_v034") HUB_DIR = Path("/workspace/multi-ai-hub/exp002_v034") OBS_DOC = Path("/workspace/srp-observatory/docs/experiments/SRP_EXPERIMENT_002_STATE_TABLE_V0_3_4.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 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 self.ep.T_recovery_recognized = max(self.ep.T_cross_obs, self.ep.T_confirm) elif self.ep.T_confirm is not None: self.ep.cross_preceded_confirm = False self.ep.T_recovery_recognized = self.ep.T_confirm 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: skip refractory_polls after the closing poll before new onset allowed. # Closing poll itself cannot start a new onset; next poll is first eligible if refractory_polls=1 # meaning: after close, remain blocked for refractory_polls subsequent polls? # v0.3: "next onset search begins at the poll AFTER recovery" # So on the closing poll we don't seek; starting next poll we can. refractory_polls=1 # implemented as: set remaining=0 but state goes to SEEK only after this arrival finishes, # and we refuse onset on the same poll. For multi-poll: after close, next arrival may onset. self.refractory_remaining = 0 # next distinct poll may onset 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 # deeper trough still updates r50 while unrecovered 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 # limits if self.ep.unrecovered_followup_polls >= int(self.p["R_followup_max_polls"]): self._close_episode("CENSORED_NO_CROSS", "unrecovered_followup_poll_limit", t, x) return if self.ep.T_confirm is not None and self.ep.onset_day is not None: # use day - confirm day; we store confirm day via history confirm_day = None for (ht, hx, hd) in self.history: if ht == self.ep.T_confirm: confirm_day = hd break if confirm_day is not None and (day - confirm_day) > int(self.p["R_days_max"]): self._close_episode("CENSORED_NO_CROSS", "unrecovered_day_limit", t, x) 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): return sm.closed_episodes[0].label in ("RELAPSED", "RECOVERED_CURRENT") or True, "ep1_closed" 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 main() -> Dict[str, Any]: builders = [ ("A_cross_then_confirm_current", case_A), ("B_cross_then_relapse_historical", case_B), ("C_deeper_trough_K_of_next_3_does_not_confirm", case_C), ("C_contrast_unbounded_append_VARIANT_NOT_DEFAULT", case_C_contrast_unbounded), ("D_same_poll_cross_and_confirm", case_D), ("E_end_of_series_censored", 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), ] 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()