fix: let assembly defects pierce the gate wrappers

Independent verification caught that the split shipped in the previous
commit did not actually hold on the only path production uses. The gate
wrappers re-raise GovernanceBackendError but nothing else, so
SourceNotConfiguredError fell into the following `except Exception` and
came back out as a governance_backend_down failure with retry_after_s=5.0.
A misconfigured source name would still retry forever and never surface.

The existing tests missed it because both of them call the private _cfg()
directly, one layer below the wrapper the governance loops actually go
through. The regression test goes through QuotaGate.

telemetry.py has to widen its terminal catch in the same commit: once the
wrapper stops relabeling the error, it is no longer a GovernanceBackendError,
and it is raised before any attempt exists, so the path would have recorded
no telemetry at all.

Also corrects the leak path count from three to five. QuotaGate.stats and
BreakerGate.retry_after_s are not wrapped by _record_quietly either.
This commit is contained in:
2026-08-06 05:57:50 -04:00
parent 8ced49a515
commit a57a5cea72
7 changed files with 99 additions and 31 deletions
@@ -20,7 +20,7 @@
| Issue 原文 | 实际情况 |
|---|---|
| 泄漏路径为 `try_enter` / `try_acquire` 两条 | ****`middleware/retry.py:216` 每轮循环开头的 `progress_age_s()` 同样在 catch 之外,直达调用方 |
| 泄漏路径为 `try_enter` / `try_acquire` 两条 | ****(设计初稿写"三条",2026-08-06 独立验证时核出遗漏两条并订正): `QuotaGate``try_acquire` / `stats`(`retry.py:249`)/ `progress_age_s`(`retry.py:216``:305`),`BreakerGate``try_enter` / `retry_after_s`(`retry.py:292``:310`)。判据是该调用点是否被 `_record_quietly` 包裹——未包裹即直达调用方;OCR 与 Embedding 两个治理循环有同构的对应点 |
| (未提及构造点数量) | 全库 **22 处** `raise GovernanceBackendError`,分布于 4 个文件 |
| 方向 A 只需改类型树 | 其中 **2 处语义完全不同**(见 §3.4),整类归入"可重投"会制造镜像 bug |
| `retry_after_s` 取 0,「docstring 已写 0 = 可立即重试,语义上是通的」 | 语义通,**工程上不通**。见 §3.2 |