fix: batch A — bugi crashowe, wyciek i kruchość startowa #8

Merged
gitea merged 1 commits from fix-batch-a into main 2026-08-01 11:51:49 +00:00
Owner

Siedem potwierdzonych usterek z analizy — każda mała i niskiego ryzyka. Jeden PR, zgodnie z planem (część A).

Crashe / złe wyniki

  • Cykliczne wypowiedzi (ai_functions.py): random.randint(0, len(CYCLIC_WORDS)) inkluzywne → mogło zwrócić lenIndexError. Teraz randrange(len) + guard na pusty słownik.
  • Losowy Fansadox (librarian_commands.py): randrange(0, len-1) nigdy nie wybierał ostatniego i wywalał ValueError('empty range') przy jednym pliku. Teraz randrange(len) + guard.
  • Generowanie obrazka (ai_commands.py): każdy except DALL-E odpowiadał, ale nie robił return → fall-through do if response: z niezdefiniowanym responseUnboundLocalError tuż po ładnym komunikacie. Każda gałąź robi teraz return; response pre-inicjalizowane; PermissionDeniedError już nie przekazuje krotki (message, text) jako jednego argumentu.
  • Dopasowanie DOI (search_bot.py): item["DOI"] in data to był substring → DOI będący prefiksem dłuższego (10.1/1 vs 10.1/12) dawał fałszywe „istnieje". Teraz dokładne dopasowanie pierwszego tokenu linii, przez indeks dict O(1) budowany raz na konsumenta (kasuje też skan O(liczba-DOI) na linię — realny zysk przy dużych bazach, wprost pod rozbudowę librariana).

Wyciek / kruchość

  • scan_incoming (communication_subroutine.py): dopasowane rekordy nigdy nie znikały z awaiting_q → narastanie przez cały uptime + ryzyko powtórnego dopasowania na powtórzonym UUID. Teraz usuwane po wysłaniu.
  • id3: (get("icy-name") or "").title() — stream bez nagłówka nie wywali już AttributeError (500 na /prepped_tracks „next").
  • scan_tracks (betoniarka): czeka na logi radia zamiast ginąć FileNotFoundError na świeżym deployu (co po cichu zabijało forwarder „co leci" do restartu).

Weryfikacja

tests/unit/test_search_bot.py + przypadki exact-match i trailing-metadata; pełny job unit 43 passed; kompilacja całości czysta.

Spostrzeżenia na dalej (nie crashują na normalnej ścieżce)

  • blok obrazka ma jeszcze zaszyte /home/pi/oserror.png fallbacki (nie istnieją w dockerze → wtórny błąd, gdy główny zapis padnie) i martwą gałąź FileNotFoundError po OSError;
  • tailer betoniarki wciąż podatny na rotację logu w trakcie działania (os.stat w pętli);
  • fix DOI zakłada „DOI = pierwszy token linii" — warto potwierdzić z realnym formatem bazy; ten dict-lookup to naturalny wstęp do indeksu DOI przy rozbudowie librariana.
Siedem potwierdzonych usterek z analizy — każda mała i niskiego ryzyka. Jeden PR, zgodnie z planem (część A). ## Crashe / złe wyniki - **Cykliczne wypowiedzi** ([ai_functions.py](ai_functions.py)): `random.randint(0, len(CYCLIC_WORDS))` inkluzywne → mogło zwrócić `len` → `IndexError`. Teraz `randrange(len)` + guard na pusty słownik. - **Losowy Fansadox** ([librarian_commands.py](librarian_commands.py)): `randrange(0, len-1)` nigdy nie wybierał ostatniego i wywalał `ValueError('empty range')` przy jednym pliku. Teraz `randrange(len)` + guard. - **Generowanie obrazka** ([ai_commands.py](ai_commands.py)): każdy `except` DALL-E odpowiadał, ale **nie robił `return`** → fall-through do `if response:` z niezdefiniowanym `response` → `UnboundLocalError` tuż po ładnym komunikacie. Każda gałąź robi teraz `return`; `response` pre-inicjalizowane; `PermissionDeniedError` już nie przekazuje krotki `(message, text)` jako jednego argumentu. - **Dopasowanie DOI** ([search_bot.py](conjurer_librarian/search_bot.py)): `item["DOI"] in data` to był **substring** → DOI będący prefiksem dłuższego (`10.1/1` vs `10.1/12`) dawał fałszywe „istnieje". Teraz dokładne dopasowanie pierwszego tokenu linii, przez **indeks dict O(1)** budowany raz na konsumenta (kasuje też skan O(liczba-DOI) na linię — realny zysk przy dużych bazach, wprost pod rozbudowę librariana). ## Wyciek / kruchość - **`scan_incoming`** ([communication_subroutine.py](communication_subroutine.py)): dopasowane rekordy nigdy nie znikały z `awaiting_q` → narastanie przez cały uptime + ryzyko powtórnego dopasowania na powtórzonym UUID. Teraz usuwane po wysłaniu. - **`id3`**: `(get("icy-name") or "").title()` — stream bez nagłówka nie wywali już `AttributeError` (500 na `/prepped_tracks` „next"). - **`scan_tracks`** (betoniarka): czeka na logi radia zamiast ginąć `FileNotFoundError` na świeżym deployu (co po cichu zabijało forwarder „co leci" do restartu). ## Weryfikacja `tests/unit/test_search_bot.py` + przypadki exact-match i trailing-metadata; pełny job `unit` **43 passed**; kompilacja całości czysta. ## Spostrzeżenia na dalej (nie crashują na normalnej ścieżce) - blok obrazka ma jeszcze zaszyte `/home/pi/oserror.png` fallbacki (nie istnieją w dockerze → wtórny błąd, gdy główny zapis padnie) i martwą gałąź `FileNotFoundError` po `OSError`; - tailer betoniarki wciąż podatny na rotację logu w trakcie działania (`os.stat` w pętli); - fix DOI zakłada „DOI = pierwszy token linii" — warto potwierdzić z realnym formatem bazy; ten dict-lookup to naturalny wstęp do indeksu DOI przy rozbudowie librariana.
gitea self-assigned this 2026-07-31 21:01:54 +00:00
gitea added 1 commit 2026-07-31 21:01:55 +00:00
fix: batch A - crash bugs, a leak, and startup/edge fragility
CI / compile (pull_request) Successful in 10s
CI / unit (pull_request) Successful in 19s
CI / integration (pull_request) Successful in 10s
build / build (push) Failing after 46m40s
CI / compile (push) Successful in 20s
CI / unit (push) Successful in 19s
CI / integration (push) Successful in 16s
defc482a22
Seven confirmed defects from the code audit, each small and low-risk.

* ai_functions.get_random_cyclic_message: random.randint(0, len(CYCLIC_WORDS))
  is inclusive -> could return len -> IndexError. Now randrange(len) + guard on
  an empty CYCLIC_WORDS.
* librarian_commands.get_image_sadox: random.randrange(0, len(res)-1) never
  picked the last comic and raised ValueError('empty range') on a single file.
  Now randrange(len) + an empty-dir guard.
* ai_commands image generation: every DALL-E error branch replied but did not
  return, so control fell through to `if response:` with response unbound ->
  UnboundLocalError right after the friendly message. Each branch now returns;
  response is pre-initialised; and PermissionDeniedError no longer passes a
  (message, text) tuple as a single arg.
* search_bot DOI match: `item["DOI"] in data` was a substring test, so a DOI
  that is a prefix of a longer one (10.1/1 vs 10.1/12) produced a false 'exists'
  hit. Now matches the line's first whitespace token exactly, via an O(1) dict
  index built once per consumer (also removes the O(queried-DOIs) per-line scan
  - a real win for large databases).
* communication_subroutine.scan_incoming: matched records were never removed
  from awaiting_q, so it grew unbounded over uptime and a reused UUID could
  re-match a stale record. Matched records are now dropped after dispatch.
* communication_subroutine.id3: (resp.headers.get("icy-name") or "").title()
  guards against a stream that omits headers (was AttributeError on None,
  500-ing the /prepped_tracks "next" handler).
* betoniarka.scan_tracks: waits for the radio logs to exist instead of dying
  with FileNotFoundError on a fresh deploy (which silently killed the
  now-playing forwarder until a restart).

Verified: tests/unit/test_search_bot.py gains exact-match and trailing-metadata
cases; full unit job 43 passed. Remaining observations (image-gen stale
/home/pi fallback paths + dead FileNotFoundError-after-OSError branch; tailer
still vulnerable to mid-run log rotation; DOI-first-token assumption) noted for
follow-up - none are crashes on the normal path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gitea merged commit defc482a22 into main 2026-08-01 11:51:49 +00:00
gitea deleted branch fix-batch-a 2026-08-01 11:51:51 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: gitea/conjurer#8