fix(projekt-matching): break infinite alert loop (issue 2)

Root cause chain (evidence: 25+ failed traces on nproj/3022414):
straight quote in page text ends the guided-decoding JSON string
early -> grammar deadlock -> whitespace loop until context/token
limit -> chat_json fails 3x (~35 min) -> IMAP conn idles out ->
move_to_trash raises -> mail never trashed -> reprocessed forever.

- ingest/mailer: MailBox.ensure_alive() reconnect before finalize;
  move_to_trash failure falls back to flag_checked, run continues
- stages: page_text() feeds only div.content minus skill-tag cloud
  to the LLM and maps quote chars to apostrophes (the actual
  degeneration trigger, live-verified: extract now OK in 21 s)
- llm: requirements maxItems 40; max_tokens 8192 caps degenerate
  runs at ~100 s instead of ~10 min

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
tlg
2026-07-14 10:33:52 +02:00
parent f40a5bb0f8
commit 36eac66abc
8 changed files with 208 additions and 13 deletions

View File

@@ -16,12 +16,31 @@ KEYWORD = "$ProjektChecked"
class MailBox:
def __init__(self, user, password, conn=None):
self.conn = conn or imaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT)
if conn is None:
self.conn.login(user, password)
self.user, self.password = user, password
self.conn = conn or self._connect()
self.conn.select("INBOX")
self._trash = None
def _connect(self):
conn = imaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT)
conn.login(self.user, self.password)
return conn
def ensure_alive(self):
"""Reconnect if the server dropped the connection (idle timeout)."""
try:
typ, _ = self.conn.noop()
if typ == "OK":
return
except Exception: # noqa: BLE001
pass
try:
self.conn.logout()
except Exception: # noqa: BLE001
pass
self.conn = self._connect()
self.conn.select("INBOX")
def unchecked_uids(self):
typ, data = self.conn.uid("SEARCH", None, f"UNKEYWORD {KEYWORD}")
if typ != "OK" or not data or not data[0]: