feat: add stall_ms and stall_count telemetry columns

Third approved port change: TelemetryRecorder.record_llm_call gains
stall_ms/stall_count (assembly gate derives from this signature).
Schema five edit points (both DDLs, both backfill decls, COLUMNS)
with INTEGER nullable columns appended last; telemetry_schema_sql
regenerates from the same constants untouched. Emitter forwards the
pair from retry._emit on the success branch only (cancelled hedge
loser rows stay NULL by construction); cache-hit and terminal rows
pass explicit None. Both **fields recorders pick the columns up with
zero signature change.
This commit is contained in:
2026-09-14 01:16:33 -04:00
parent 53bcf43172
commit e0b0be09a7
8 changed files with 220 additions and 25 deletions
+17
View File
@@ -142,6 +142,10 @@ class RecordingEmitter:
error,
reasoning_applies,
operation,
# 1.3.8: `_emit` 显式转发 stall 两值(审查 B1)——显式全签名 fake 不加则 TypeError;
# 默认 None 只护调用方,护不住被调的显式签名
stall_ms=None,
stall_count=None,
):
self.rows.append(
{
@@ -151,6 +155,9 @@ class RecordingEmitter:
if request.call_context is not None
else None,
"error": error,
# 1.3.8: 逐行捕获 stall 归因(取消输家行须恒 None,设计 §11-B2)
"stall_ms": stall_ms,
"stall_count": stall_count,
}
)
@@ -455,6 +462,16 @@ class TestHedgeSettlementAndSignals:
assert winner["error"] is None
assert loser["logical_call_id"] == winner["logical_call_id"] == ctx.logical_call_id
async def test_hedge_cancelled_row_has_null_stall(self):
"""被取消输家无 TransportResult: `hedge_cancelled` 行 stall 两值恒 None(1.3.8 B2)。"""
emitter = RecordingEmitter()
transport = HedgeTransport({"s1": [("hang",)], "s2": [("succeed", "win")]})
mw, _, _ = _harness([_src("s1"), _src("s2")], transport, emitter=emitter)
await asyncio.wait_for(mw(_req()), timeout=5)
loser = next(r for r in emitter.rows if r["error"] == "hedge_cancelled")
assert loser["stall_ms"] is None
assert loser["stall_count"] is None
async def test_loser_does_not_feed_breaker(self):
"""输家取消不喂熔断失败计数、不喂健康分;赢家照常 record_success(④)。"""
selector = RecordingSelector()