feat: let tpm be configured without an est_tokens companion

The gate check forced operators to guess a per-call token size before
they could enable the TPM gate at all; est_tokens is now an optional
tuning override and effective_est_tokens() derives the reservation from
the provider quota. Reservation and settlement already read the same
derived value, so the deposit still nets to zero on both the success
path and the non-dead transient failure path.

The rest of _validate_gates is untouched, and the est_tokens field plus
its EST_TOKENS env key stay put for migration compatibility.
This commit is contained in:
2026-07-30 10:57:40 -04:00
parent cd8bebba00
commit ab496bb298
4 changed files with 62 additions and 21 deletions
+17
View File
@@ -84,6 +84,23 @@ class TestTpmGate:
await p2.release()
assert (await limiter.source_stats("s1")).tpm_used == 600
async def test_settle_equal_to_prededuct_keeps_deposit(self, limiter_factory):
"""预扣量与结算量同为派生值时,双后端都必须留存押金(delta==0)。
这里只锁**后端算术**: 相等的两个数进出,窗口残留量恰为该值。
"调用点是否真的取了派生值"是编排行为,由 tests/unit/test_retry.py
经 RetryMW 端到端覆盖,不在本契约文件重复(否则只是自证同一个入参)。
"""
src = make_source(tpm=6000, est_tokens=0) # 派生值 = max(1, 6000 // 60) = 100
derived = src.effective_est_tokens()
assert derived == 100
limiter = limiter_factory([src], _NO_GLOBAL)
permit = await limiter.try_acquire("s1", derived)
assert permit is not None
await permit.settle(derived)
await permit.release()
assert (await limiter.source_stats("s1")).tpm_used == derived
async def test_failed_acquire_leaves_no_tpm_trace(self, limiter_factory):
src = make_source(tpm=500, est_tokens=400)
limiter = limiter_factory([src], _NO_GLOBAL)