Die Sperre gegen parallele Laeufe schuetzt gegen Ueberholen, nicht gegen
einen Lauf, der gar nicht zurueckkommt. Haengt ein Browser oder steht eine
Verbindung, laeuft das finally nie - pipelineRunning bleibt true und der
Scanner sucht bis zum naechsten Neustart des Dienstes nicht mehr. Das ist
schlimmer als die Ueberlappung, gegen die die Sperre da ist.
Jetzt merkt sich die Pipeline den Startzeitpunkt. Steht die Sperre laenger
als eine Stunde, gibt der naechste Aufruf sie frei und startet neu.
Dokumentiert in docs/03-scanner.md, mit den Messwerten des Vorfalls vom
26.08.2026 in der alten App: fuenf Pipeline-Starts in 16 Minuten, 31
chrome-headless-shell-Prozesse, 1,62 GB RSS, Load Average 126 auf 4 Kernen,
Swap zu 100 Prozent gefuellt. Die alte App hatte die Sperre nicht; der
Guard dort ist mit diesem Commit nachgezogen worden.
Zur Abgrenzung festgehalten: die Chromium-Prozesse waren kein Leck. Alle
Scraper schliessen ihren Browser in einem finally. Es waren fuenf
gleichzeitige Laeufe mal mehrere Boersen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`last_scan_at` hielt das ENDE des Laufs fest. Der Scheduler rechnet die
Fälligkeit aber von diesem Wert aus, also kam die Laufzeit von drei bis
vier Minuten jedes Mal obendrauf: bei Einstellung 15 Minuten lagen die
Läufe 19 Minuten auseinander.
Gemessen heute früh, unsere Instanz gegen das Original:
wir: 07:00 07:19 07:38 07:57 08:16 → 19 Min
Original: 07:00 07:16 07:31 07:47 08:03 → 15-16 Min
Über das Fenster 07-22 Uhr sind das rund 47 statt 58 Durchgänge pro Tag.
Die Trefferliste der Börsen zeigt nur die neuesten Projekte, jeder
ausgelassene Durchgang kostet deshalb echte Funde.
Jetzt wird der Startzeitpunkt gespeichert. Das `finally` bleibt, damit
sich ein gescheiterter Lauf nicht sofort wiederholt; dauert ein Lauf
länger als das Intervall, ist der nächste sofort fällig - die Sperre aus
demselben Commit verhindert die Überlappung.
Kein Rückschritt zum vorherigen Fehler (e58b94f): der Zeitstempel liegt
weiterhin in der Datenbank und trägt den Zeitplan über Neustarts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Der Dateibaum im README und in 05/08, die Kommentare hinter # in den
Beispielbefehlen und die Beschriftungen in Mermaid-Sequenzdiagrammen sind
Lesetext, stehen aber in Codeblöcken - die erste Runde hat sie deshalb
übersprungen.
Die Befehle selbst und alle Bezeichner bleiben unverändert: ersetzt wurde
nur hinter "# ", hinter den zwei Leerzeichen des Dateibaums und hinter
": " in Mermaid-Nachrichten. ER-Labels wie bewertet_fuer sind Bezeichner
und bleiben, "systemctl daemon-reload" ebenfalls.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
893 Stellen in Fliesstext, Tabellen und Mermaid-Beschriftungen. Dazu die
sichtbaren ss-Fälle (ausschließlich, groß, außen, schließt, heißt,
draußen, weiße, anstoßen, Straße).
Nicht angetastet, damit die Doku weiter beschreibt, was wirklich in der
Datenbank steht:
- Codeblöcke und Code-Spans - dort stehen Bezeichner, SQL, Enum-Werte
und Dateinamen: `mail_pool`, `'uebernommen'`, `extract.js`
- in Mermaid nur der Text in Anführungszeichen; Knotennamen, Pfeile und
Werte wie source = 'Mail' bleiben, wie sie sind
- `empfaenger` im ER-Diagramm von crm_mail_contact_candidates: die
Aufzählung dort nennt die Datenbankwerte, nicht die Anzeige
Die Menü-Skizze in der Benutzeranleitung zeigt jetzt die aufgeklappten
Kontakte mit "Aus Mails" samt Zähler.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Die Sperrfrist nach dem Start begann bei jedem Neustart von vorn. Zwei
Deployments im Abstand von zehn Minuten haben den faelligen Suchlauf damit
zweimal um eine halbe Stunde verschoben - er fand nie statt. Wer oefter als
alle SCANNER_SCHEDULER_OFFSET Minuten deployt, hat einen Scanner, der
automatisch nie sucht.
Der Zeitpunkt des letzten Laufs liegt jetzt in settings.last_scan_at, von der
Pipeline geschrieben (auch nach einem Abbruch, damit sich ein gescheiterter
Lauf nicht sofort wiederholt). Der Versatz greift nur noch, solange dort nichts
steht - also beim allerersten Start dieser Instanz, wo er gebraucht wird.
Der Scheduler liest den Wert zusaetzlich in jedem Tick: ein manueller Scan aus
der Oberflaeche schreibt ihn, weiss aber nichts von der Variablen im Scheduler.
Ohne den Abgleich konnte der Scheduler direkt hinter einem manuellen Lauf noch
einmal losziehen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Der eigenstaendige Projekt Scanner zieht komplett ins CRM ein und laeuft
parallel zur bestehenden App weiter - jede geteilte Ressource ist verdoppelt
statt geteilt, damit die alte Installation unberuehrt bleibt:
- eigene Datenbank perinco_scanner, legt sich beim Start selbst an
- eigener SCANNER_ENCRYPTION_KEY, eigenes Upload-Verzeichnis
- eigener Scheduler mit Zeitversatz, damit nicht beide Instanzen gleichzeitig
mit denselben Portal-Cookies scrapen (Rate-Limits, CAPTCHA, doppelter RAM)
- Sperre gegen ueberlappende Laeufe: Scheduler und "Jetzt scannen" teilen sie,
ein zweiter Aufruf bekommt 409 statt doppelter OpenAI-Kosten
Backend unter /api/scanner hinter der CRM-Anmeldung; die HTTP-Basic-Auth des
Originals entfaellt. CV-PDFs kommen als Stream mit Token statt aus einem
statischen Verzeichnis. Bestandsdaten holt "npm run scanner:import" aus der
alten Datenbank, ohne sie zu veraendern.
Oberflaeche in Tailwind und CI-Farben neu gebaut: Menuepunkt Scanner mit
aufklappbaren Unterpunkten, Einstellungen als eigener Reiter.
Whisper hat keinen eigenen Menuepunkt mehr, sondern ist ein Reiter in den
Einstellungen. Damit diese Geraeteeinstellung jeder Rolle offensteht, sind die
Einstellungen jetzt fuer alle sichtbar - welche Reiter erscheinen, entscheidet
die Rolle.
Ausserdem: kein festes Admin-Passwort mehr im Quelltext, playwright exakt auf
1.60.0 gepinnt (nutzt den vorhandenen Chromium-Build des Servers), technische
Dokumentation mit Funktionszeichnungen unter docs/.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>