Skip to content

Commit 1735c71

Browse files
Pigbibicodex
andcommitted
fix: reject mixed gateway epoch batches
Co-Authored-By: Codex <noreply@openai.com>
1 parent c1e46eb commit 1735c71

2 files changed

Lines changed: 9 additions & 3 deletions

File tree

scripts/gateway_recovery_decision.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class Decision(str, Enum): TERMINAL='terminal'; READY='ready'; PROGRESS='progres
99
@dataclass(frozen=True)
1010
class Epoch: container_id:str; started_at:str; lower_bound:str
1111
@dataclass(frozen=True)
12-
class Event: container_id:str; line:str
12+
class Event: container_id:str; started_at:str; line:str
1313
@dataclass(frozen=True)
1414
class Result: decision:Decision; sticky_terminal:bool
1515
STAMP=re.compile(r'^(\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d)(?:\.(\d{1,9}))?Z\s+(.*)$')
@@ -22,9 +22,11 @@ def key(value:str):
2222
def decide(epoch:Epoch, events:Iterable[Event], *, stable_ready:bool=False, prior_sticky:bool=False, current_epoch:Epoch|None=None)->Result:
2323
if current_epoch and (current_epoch.container_id!=epoch.container_id or current_epoch.started_at!=epoch.started_at): return Result(Decision.EPOCH_CHANGED,False)
2424
if prior_sticky:return Result(Decision.TERMINAL,True)
25+
events=tuple(events)
26+
if any(item.container_id!=epoch.container_id or item.started_at!=epoch.started_at for item in events): return Result(Decision.NONE,False)
2527
lower=key(epoch.lower_bound); ready=stable_ready; progress=False; terminal=False
2628
for item in events:
27-
if item.container_id!=epoch.container_id:continue
29+
if item.container_id!=epoch.container_id or item.started_at!=epoch.started_at:continue
2830
match=STAMP.match(item.line)
2931
if not match:continue
3032
stamp=(datetime.fromisoformat(match.group(1)).replace(tzinfo=timezone.utc),int(((match.group(2)or'')+'0'*9)[:9]))

tests/test_gateway_recovery_decision.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
55
from scripts.gateway_recovery_decision import Decision, Epoch, Event, decide
66
E=Epoch('c','2026-07-15T16:00:00.123456789Z','2026-07-15T16:00:00.123456789Z')
7-
def e(text, cid='c'): return Event(cid,text)
7+
def e(text, cid='c', started_at=E.started_at): return Event(cid,started_at,text)
88
class Contract(unittest.TestCase):
99
def test_precedence_permutations(self):
1010
terminal=e('2026-07-15T16:00:02Z IBC closing because login has not completed'); ready=e('2026-07-15T16:00:01Z READY'); progress=e('2026-07-15T16:00:01Z IBC: Login attempt')
@@ -19,4 +19,8 @@ def test_sticky_cursor_and_drift(self):
1919
drift=Epoch('c','2026-07-15T16:01:00.000000000Z','2026-07-15T16:01:00.000000000Z'); self.assertEqual(decide(E,[],current_epoch=drift,prior_sticky=True).decision,Decision.EPOCH_CHANGED)
2020
def test_filters(self):
2121
self.assertEqual(decide(E,[e('2026-07-15T16:00:00.123456788Z IBC: Login attempt'),e('2026-07-15T16:00:01Z IBC: Login attempt','old')]).decision,Decision.NONE)
22+
def test_mixed_or_unprovable_event_identity_is_rejected(self):
23+
mixed=[e('2026-07-15T16:00:01Z IBC closing because login has not completed'), e('2026-07-15T16:00:01Z READY',started_at='2026-07-15T16:01:00.000000000Z')]
24+
self.assertEqual(decide(E,mixed).decision,Decision.NONE)
25+
self.assertEqual(decide(E,[Event('c','', '2026-07-15T16:00:01Z IBC: Login attempt')]).decision,Decision.NONE)
2226
if __name__=='__main__': unittest.main()

0 commit comments

Comments
 (0)