feat: suppress consecutive-channel opening on evidently healthy sources

Round 8 forensics caught the healthy source circuit-opened by five
random empty completions (~20% ambient failure rate makes a 5-streak
land every ~3000 attempts), blacking out the only good source for 60s.
When the window holds min_calls samples below the failure-rate
threshold, a streak is noise and no longer opens the gate; cold-start
and low-traffic semantics are unchanged and sudden death of a warm
source is still caught by the rate channel.
This commit is contained in:
2026-07-21 15:28:34 -04:00
parent 0e0d599441
commit 121888a0eb
4 changed files with 60 additions and 5 deletions
+12 -2
View File
@@ -81,6 +81,14 @@ class InMemoryGate:
if failed:
g.f0 += 1
def _window_evidently_healthy(self, g: _SourceGate) -> bool:
"""窗口样本充足且失败率低于阈值 = 有充分健康证据(迭代 6 抑制判据)。"""
self._rotate_window(g)
attempts = g.a0 + g.a1
if attempts < self._cfg.min_calls:
return False
return (g.f0 + g.f1) / attempts < self._cfg.fail_rate
def _rate_channel_open(self, g: _SourceGate) -> bool:
self._rotate_window(g)
attempts = g.a0 + g.a1
@@ -227,8 +235,10 @@ class InMemoryGate:
g.fails += 1
if self._rate_channel_open(g):
self._open(g, reason, bump_streak=True)
elif g.fails >= self._cfg.fail_threshold:
self._open(g, reason, bump_streak=False) # 连续通道不递增(C1 封顶)
elif g.fails >= self._cfg.fail_threshold and not self._window_evidently_healthy(g):
# 连续通道不递增(C1 封顶);窗口证据充足且健康时连败是噪声,
# 不误熔"当前最好的源"(迭代 6,第八轮实证: 源1 被 5 连空补全误关 60s)
self._open(g, reason, bump_streak=False)
return self._snapshot(g, applied=True)
async def release_probe(self, entry: GateDecision) -> GateUpdate: