fix(question_gen): raise ValueError on UPDATE of missing rows

record_run_end, update_gates, and update_difficulty now check
cursor.rowcount after UPDATE+commit and raise ValueError if 0 rows
were affected. Prevents silent telemetry loss.

Adds three negative-path tests:
- test_record_run_end_missing_run_raises
- test_update_gates_missing_item_raises
- test_update_difficulty_missing_item_raises

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-11 23:23:48 -04:00
parent 7abe92eb1c
commit 9627ac9cf9
2 changed files with 33 additions and 3 deletions
+9 -3
View File
@@ -212,7 +212,7 @@ class QuestionGenStore:
批次统计摘要。 批次统计摘要。
""" """
now = datetime.now(tz=UTC).isoformat(timespec="seconds") now = datetime.now(tz=UTC).isoformat(timespec="seconds")
self._conn.execute( cursor = self._conn.execute(
""" """
UPDATE question_gen_runs UPDATE question_gen_runs
SET ended_at=?, status=?, total_slots=?, accepted=?, rejected=?, heavy_sampled=? SET ended_at=?, status=?, total_slots=?, accepted=?, rejected=?, heavy_sampled=?
@@ -229,6 +229,8 @@ class QuestionGenStore:
), ),
) )
self._conn.commit() self._conn.commit()
if cursor.rowcount == 0:
raise ValueError(f"run_id 不存在: {run_id}")
logger.info( logger.info(
"出题批次已结束: run_id={}, status={}, accepted={}/{}", "出题批次已结束: run_id={}, status={}, accepted={}/{}",
run_id, run_id,
@@ -307,7 +309,7 @@ class QuestionGenStore:
属性,每个属性具有 .verdict.value 和 .reason;以及 passed/reject_reason 属性)。 属性,每个属性具有 .verdict.value 和 .reason;以及 passed/reject_reason 属性)。
""" """
final_status = "accepted" if report.passed else "rejected" final_status = "accepted" if report.passed else "rejected"
self._conn.execute( cursor = self._conn.execute(
""" """
UPDATE question_gen_items UPDATE question_gen_items
SET gate_key_verify=?, gate_blind_answer=?, gate_multi_true=?, SET gate_key_verify=?, gate_blind_answer=?, gate_multi_true=?,
@@ -325,6 +327,8 @@ class QuestionGenStore:
), ),
) )
self._conn.commit() self._conn.commit()
if cursor.rowcount == 0:
raise ValueError(f"item_id 不存在: {item_id}")
def update_difficulty(self, item_id: str, difficulty_steps: int) -> None: def update_difficulty(self, item_id: str, difficulty_steps: int) -> None:
"""更新重量抽检产出的 Agent 步数。 """更新重量抽检产出的 Agent 步数。
@@ -336,11 +340,13 @@ class QuestionGenStore:
difficulty_steps : int difficulty_steps : int
Agent 完成该题所需步数。 Agent 完成该题所需步数。
""" """
self._conn.execute( cursor = self._conn.execute(
"UPDATE question_gen_items SET difficulty_steps=? WHERE item_id=?", "UPDATE question_gen_items SET difficulty_steps=? WHERE item_id=?",
(difficulty_steps, item_id), (difficulty_steps, item_id),
) )
self._conn.commit() self._conn.commit()
if cursor.rowcount == 0:
raise ValueError(f"item_id 不存在: {item_id}")
def get_run_stats(self, run_id: str) -> RunStats: def get_run_stats(self, run_id: str) -> RunStats:
"""查询批次统计摘要。 """查询批次统计摘要。
+24
View File
@@ -242,3 +242,27 @@ class TestQuestionGenStore:
"""查询不存在的 run_id 应报错。""" """查询不存在的 run_id 应报错。"""
with pytest.raises(ValueError, match="run_id"): with pytest.raises(ValueError, match="run_id"):
store.get_run_stats("nonexistent-run") store.get_run_stats("nonexistent-run")
def test_record_run_end_missing_run_raises(self, store: QuestionGenStore) -> None:
"""对不存在的 run_id 调用 record_run_end 应报错。"""
stats = RunStats(total_slots=10, accepted=5, rejected=3, heavy_sampled=2)
with pytest.raises(ValueError, match="run_id"):
store.record_run_end(
run_id="ghost-run", status="completed", stats=stats
)
def test_update_gates_missing_item_raises(self, store: QuestionGenStore) -> None:
"""对不存在的 item_id 调用 update_gates 应报错。"""
report = _MockGateReport(
key_verify=_GateResult(_Verdict.PASS, "ok"),
blind_answer=_GateResult(_Verdict.PASS, "ok"),
multi_true=_GateResult(_Verdict.PASS, "ok"),
leak_test=_GateResult(_Verdict.PASS, "ok"),
)
with pytest.raises(ValueError, match="item_id"):
store.update_gates(item_id="ghost-item", report=report)
def test_update_difficulty_missing_item_raises(self, store: QuestionGenStore) -> None:
"""对不存在的 item_id 调用 update_difficulty 应报错。"""
with pytest.raises(ValueError, match="item_id"):
store.update_difficulty(item_id="ghost-item", difficulty_steps=5)