diff --git a/lifecycle/graph_miners.py b/lifecycle/graph_miners.py index 3ff81d6..c3ad819 100644 --- a/lifecycle/graph_miners.py +++ b/lifecycle/graph_miners.py @@ -1283,16 +1283,17 @@ async def miner_zero_results(cm: AsyncConnectionManager, layer: str) -> dict[str if not rows: return {"edges": 0} surfaced = 0 - try: - from graph.epistemic import EpistemicGraph - - g = EpistemicGraph(cm=cm, layer=layer) - for r in rows: - await g.find_or_add_entity(str(r["user_id"]), str(r["query"])[:300], "question") - surfaced += 1 - except Exception: - logger.debug("zero-result surfacing failed", exc_info=True) - return {"edges": 0} + # No try/except here, for the same reason `miner_embedding` has none: a + # failure that returns `{"edges": 0}` is indistinguishable from "there was + # nothing to surface", and this one logged at `debug`, which is invisible + # at the level this service actually runs at. Letting it escape reaches the + # handler in `graph_enrich` that records `edges: -1` with a warning. + from graph.epistemic import EpistemicGraph + + g = EpistemicGraph(cm=cm, layer=layer) + for r in rows: + await g.find_or_add_entity(str(r["user_id"]), str(r["query"])[:300], "question") + surfaced += 1 return {"edges": surfaced} diff --git a/tests/test_s17_stage1_tail.py b/tests/test_s17_stage1_tail.py index 75bc71a..4b74f6d 100644 --- a/tests/test_s17_stage1_tail.py +++ b/tests/test_s17_stage1_tail.py @@ -243,6 +243,35 @@ async def test_zero_result_single_hit_not_surfaced(migrated_cm) -> None: assert res["edges"] == 0, "один провал — не сигнал" +@pytest.mark.asyncio +async def test_zero_result_miner_lets_a_failure_escape(migrated_cm, monkeypatch) -> None: + """Сбой обязан выйти наружу, а не стать нулём с записью в `debug`. + + 07.10.2026: здесь был второй такой же глушитель, как в `miner_embedding` — + `except Exception` писал в `debug` (невидимо на рабочем уровне) и возвращал + `{"edges": 0}`, то есть «нечего выносить». Договор один на всех шахтёров: + ноль — это работа, которая не нашлась, а сбой пусть ловит `graph_enrich`. + """ + from graph.epistemic import EpistemicGraph + from lifecycle.graph_miners import log_zero_result, miner_zero_results + from shared.constants import DB_NAME + + cm = migrated_cm + conn = await cm.get(DB_NAME) + await conn.execute("DELETE FROM recall_zero_results") + await conn.commit() + for _ in range(2): + await log_zero_result(cm, "user", "s17zr3", "провал запроса, который надо вынести наверх") + + async def _boom(self, *args, **kwargs): + raise RuntimeError("find_or_add_entity is down") + + monkeypatch.setattr(EpistemicGraph, "find_or_add_entity", _boom) + + with pytest.raises(RuntimeError, match="find_or_add_entity is down"): + await miner_zero_results(cm, "user") + + # ── 7. counter-signal алиасы ─────────────────────────────────────────────────