feat(queue): Gesetze max_text und max_age — gekürzte Langtexte, verfallene Stau-Ansagen

Zwei Löcher im Anti-Spam-Gesetz, die ein Prompt trivial nutzt: Ein Agent
kann 64 KB Text einreichen (ElevenLabs stellt die Rechnung, die Ansage
dauert Minuten), und geparkte normal-Ansagen spielen unbegrenzt spät —
„Build fertig" zweieinhalb Minuten nach dem Build ist keine Nachricht.

- queue.max_text (Default 400 Zeichen, Env TALK_MAX_TEXT): längere Texte
  werden beim Eintritt an einer Wortgrenze gekürzt und so gesprochen; die
  /speak-Antwort meldet truncated, die CLI warnt auf stderr, das MCP-
  Ergebnis sagt es im Text. Runen, nicht Bytes.
- queue.max_age (Default 2m, Env TALK_MAX_AGE): Items, die beim
  Fälligwerden länger gewartet haben, werden verworfen (discarded
  "expired", Protokoll nennt Wartezeit und Grenze). now:true ist
  ausgenommen. Rang: now > veraltet > Ruhezeit > Rate-Limit.
- /status trägt "limits": {max_text, max_age}; `talk status` zeigt sie.
- Negative Werte sind Config-Fehler; 0 = unbegrenzt.

Tests: truncateText-Tabelle (Wortgrenze, Satzzeichen, Runen), Enqueue-
Kürzung, decide-Vorrang inkl. now-Ausnahme, geparktes Item verfällt;
Config-Schichten; Prozessgrenze: Provider bekommt die gekürzte Fassung,
dritte von drei Stau-Ansagen wird nie gesprochen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
m3ta-chironandClaude Fable 5.1 committed 2026-10-24 09:54:40 +02:00
1 parent 79314dfb0f
commit 97650ad0ec
13 files changed
+430 -26

No files matched your search

+4 -2
View File
@@ -164,7 +164,7 @@ func (h *handler) admitSpeak(req SpeakRequest) speakAdmission {
return speakAdmission{status: http.StatusAccepted,
resp: SpeakResponse{Discarded: adm.Discarded, Toast: adm.Toast}}
}
return speakAdmission{status: http.StatusAccepted, resp: SpeakResponse{ID: adm.ID}}
return speakAdmission{status: http.StatusAccepted, resp: SpeakResponse{ID: adm.ID, Truncated: adm.Truncated}}
}
// status behandelt GET /status: Queue-Länge (+ wartende IDs), aktuelles
@@ -195,7 +195,9 @@ func (h *handler) status(w http.ResponseWriter, r *http.Request) {
if h.provider != nil {
st.Provider = h.provider()
}
st.RateLimit, st.QuietHours = lawState(h.queue.LawState())
law := h.queue.LawState()
st.RateLimit, st.QuietHours = lawState(law)
st.Limits = &LimitsState{MaxText: law.MaxText, MaxAge: law.MaxAge.String()}
writeJSON(w, http.StatusOK, st)
}