From b3aba7c31d11c85c8c984bc1eb6428ee52a84bfe Mon Sep 17 00:00:00 2001 From: iomgaa Date: Fri, 17 Jul 2026 01:17:02 -0400 Subject: [PATCH] fix: escape all LIKE specials in step-row cleanup --- app/harness/runner.py | 23 ++++++++++++++++++++--- tests/unit/test_step_rerun_idempotent.py | 23 +++++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/app/harness/runner.py b/app/harness/runner.py index 5b0bb7b..0664f55 100644 --- a/app/harness/runner.py +++ b/app/harness/runner.py @@ -602,6 +602,21 @@ def _write_skip_report( ) +def _escape_sql_like(text: str) -> str: + """转义 SQL LIKE 模式中的全部特殊字符(`\\`、`%`、`_`)为字面匹配。 + + 参数: + text: 待作为 LIKE 前缀字面使用的原始字符串。 + + 返回: + 可安全拼入 `LIKE ? ESCAPE '\\'` 模式的转义串。 + + 关键实现细节: + 反斜杠必须最先转义,否则会二次转义后续替换产生的转义符。 + """ + return text.replace("\\", "\\\\").replace("%", r"\%").replace("_", r"\_") + + def _clear_step_rows(db_path: str, *, baseline_run_id: str, epoch: int, step: int) -> None: """清空一个 step 的全部旧行(rollout + gate 派生),保证崩溃重跑幂等。 @@ -621,13 +636,15 @@ def _clear_step_rows(db_path: str, *, baseline_run_id: str, epoch: int, step: in 无。 关键实现细节: - predictions/traces 的 gate 行按 LIKE 前缀删除,'_' 通配显式转义 - (ESCAPE)钉死字面匹配,避免 `..._s1` 误匹配 `..._s10` 类前缀陷阱。 + predictions/traces 的 gate 行按 LIKE 前缀删除,`\\`/`%`/`_` 三个 LIKE + 特殊字符全部显式转义(ESCAPE)钉死字面匹配,避免 `..._s1` 误匹配 + `..._s10` 类前缀陷阱,也防 run_id 含 `%`/`\\` 时通配误删他 run 行。 """ from app.harness.inference import PREDICTIONS_SCHEMA, TRACES_SCHEMA from app.harness.log import HarnessLog step_run_id = f"{baseline_run_id}_e{epoch}_s{step}" + escaped = _escape_sql_like(step_run_id) with HarnessLog(db_path, step_run_id, register_run=False) as log: log.create_table("predictions", PREDICTIONS_SCHEMA) log.create_table("traces", TRACES_SCHEMA) @@ -635,7 +652,7 @@ def _clear_step_rows(db_path: str, *, baseline_run_id: str, epoch: int, step: in log.execute(f"DELETE FROM {table} WHERE run_id=?", (step_run_id,)) log.execute( f"DELETE FROM {table} WHERE run_id LIKE ? ESCAPE '\\'", - (step_run_id.replace("_", r"\_") + r"\_gate\_%",), + (escaped + r"\_gate\_%",), ) for table in ("gate_evidence", "quadrant_pair"): exists = log.query( diff --git a/tests/unit/test_step_rerun_idempotent.py b/tests/unit/test_step_rerun_idempotent.py index 1a966f8..b19e85d 100644 --- a/tests/unit/test_step_rerun_idempotent.py +++ b/tests/unit/test_step_rerun_idempotent.py @@ -67,3 +67,26 @@ def test_clear_step_rows_missing_tables_is_noop(tmp_path) -> None: conn.commit() conn.close() _clear_step_rows(str(db), baseline_run_id="infer_adhoc", epoch=1, step=0) + + +def test_clear_step_rows_like_specials_in_run_id(tmp_path) -> None: + """run_id 含 % 与反斜杠时不通配误删他 run 行(LIKE 全特殊字符转义回归锁)。""" + db = tmp_path / "harness.db" + conn = sqlite3.connect(db) + conn.execute("CREATE TABLE predictions (run_id TEXT, question_id TEXT)") + conn.execute("CREATE TABLE traces (run_id TEXT, question_id TEXT)") + rows = [ + (r"we%ird\run_e1_s0", "q1"), # 本 step rollout + (r"we%ird\run_e1_s0_gate_x_base", "q2"), # 本 step gate 行 + (r"weXird\run_e1_s0_gate_x_base", "q3"), # % 若未转义会误匹配此行 + (r"we%irdXrun_e1_s0_gate_x_base", "q4"), # \ 若未转义会误匹配此行 + ] + conn.executemany("INSERT INTO predictions VALUES (?, ?)", rows) + conn.executemany("INSERT INTO traces VALUES (?, ?)", rows) + conn.commit() + conn.close() + _clear_step_rows(str(db), baseline_run_id=r"we%ird\run", epoch=1, step=0) + conn = sqlite3.connect(db) + left = {r[0] for r in conn.execute("SELECT run_id FROM predictions")} + conn.close() + assert left == {r"weXird\run_e1_s0_gate_x_base", r"we%irdXrun_e1_s0_gate_x_base"}