feat: wire the telemetry text cap through settings

`PGW_TELEMETRY_TEXT_CAP` now reaches the emitter on every assembly path.
Unset means no truncation, which stays the default: a truncated row is
no longer audit evidence and cannot be replayed, and downstreams rely on
that today. The flip side — contracts and bids sitting in `llm_calls`
indefinitely, multi-tenant — is spelled out in `.env.example` so readers
can weigh both.

All three `from_settings` paths are wired (chat, embedding, OCR): they
write the same table, so capping only chat would leave half of it
uncontrolled. `TelemetryEmitter.__init__` now rejects `text_cap <= 0`;
it is the single point where the three clients converge, so the direct
construction path — a public assembly route the settings guard never
sees — is covered too. `0` would otherwise reduce every body to a bare
elision marker.
This commit is contained in:
2026-08-19 13:57:15 -04:00
parent 33ed7ecdfc
commit c26b34e854
9 changed files with 192 additions and 0 deletions
+10
View File
@@ -1602,6 +1602,16 @@ class TestTelemetryTextCap:
assert messages[1]["content"][0]["text"] == _LONG
assert json.loads(row["messages"])[0]["content"] == _CAPPED # 落库那份确已截断
def test_non_positive_cap_rejected_at_construction(self):
"""emitter 是三个 Client 唯一的汇合点,值域校验放这一处即覆盖全部装配路。
settings 层那道只管 env;直接构造 `GatewayClient(..., text_cap=0)` 是库
承诺的另一条公共装配路,没有这道闸就会把每条正文写成一个光秃秃的省略标记。
"""
for bad in (0, -1):
with pytest.raises(ValueError, match="text_cap"):
TelemetryEmitter(_MemoryRecorder(), text_cap=bad)
class _StubEmbedTransport:
async def embed(self, *, texts, source, call_id):