From b1d3a391b76b1e5664229b66c636ab73955ead3c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 17:39:35 +0000 Subject: [PATCH] =?UTF-8?q?Workers=20P6:=20Betriebsanleitung=20nach=20der?= =?UTF-8?q?=20Endpr=C3=BCfung=20(Teil=20A=20Datenschutz,=20Teil=20B=20Scou?= =?UTF-8?q?ts,=20Resend=20und=20Weiterleitung,=20Freischalten,=20erste=20N?= =?UTF-8?q?acht,=20R=C3=BCckweg)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- .../2026-10-17-workers-p6-ohne-website.md | 436 +++++++++++++++++- 1 file changed, 424 insertions(+), 12 deletions(-) diff --git a/docs/superpowers/plans/2026-10-17-workers-p6-ohne-website.md b/docs/superpowers/plans/2026-10-17-workers-p6-ohne-website.md index 5fbe497..118700a 100644 --- a/docs/superpowers/plans/2026-10-17-workers-p6-ohne-website.md +++ b/docs/superpowers/plans/2026-10-17-workers-p6-ohne-website.md @@ -2026,17 +2026,429 @@ test('E4/A1: erweitereDeps setzt places, osm und dns, nie llm, und lässt deps.f ## Betriebsanleitung (Kenso) -Reihenfolge ist wichtig: Erst das Fundament (1–4), dann Probe (5), dann der Datenschutztext (6–7), erst danach Räume verbinden (8). Solange `privacyInteressentenLive` aus ist, geht keine Kaltmail raus (Tor-Grund `datenschutz`, die Aufträge bleiben offen). Verbinde deshalb keinen Raum vor Schritt 7: Sonst wählt die erste Nacht drei Betriebe aus, deren Erstmail-Aufträge liegen bleiben und die Mail-Plätze blockieren (keine weitere Suche, bis der Schalter an ist). Der Schalter gilt auch für Claras Erstantworten (P3). +Zwei Teile, in dieser Reihenfolge. **Teil A** bringt den Datenschutztext live und hängt nicht am Dienst. Er muss fertig sein, **bevor** du in P3 Block G (Clara einschalten) oder P4 Block H (Schalter, Dauerbetrieb) den Datenschutz-Schalter setzt, denn beide verlangen den veröffentlichten Abschnitt „Interessenten“. **Teil B** nimmt die Scouts und die automatische Kaltmail in Betrieb, erst nach P1 bis P4 (jeweils bis zu ihrem „Fertig, wenn“) und wenn P5 live ist oder du den Kaufweg bewusst von Hand machst. **[Claude]** macht Claude für dich (merged, testet, committet), alles andere tippst du selbst auf dem Server, jeder Befehl in seinem Kasten. Zugangsdaten tippst du nur selbst ein (`read -rs`), nie in einen Chat und nie in einen Befehl, der sie ausgibt; die `.env` zeigst du nie an, die Befehle unten zählen nur Zeilen. Nach jeder Änderung an `.env` oder `docker-compose.yml` startest du mit `cd /home/webklar/apps && docker compose up -d --no-deps workers` neu (nie `docker restart`: das liest `.env` nicht neu ein; nie ein nacktes `docker compose up -d`). Wird ein `git push` abgelehnt: nie `--force`, Claude Bescheid. Agenten führen von diesen Befehlen nichts aus. -1. **Voraussetzungen.** P1–P4 laufen: `docker ps --filter name=webklar-workers --format '{{.Names}} {{.Status}}'` ⇒ `webklar-workers Up …`. Absender-Domain und Postfach sind eingerichtet (SPF, DKIM, DMARC), die Aufwärmphase läuft seit mindestens zwei Wochen (Spec §11.4), eine von Hand verschickte Testmail aus dem Absender-Postfach erreicht bei mail-tester.com mindestens 9 von 10. -2. **Google-Places-Schlüssel.** Google Cloud Console: „Places API (New)“ aktivieren, Schlüssel anlegen, beschränken auf diese API und die Server-IP, Tageskontingent für Text Search auf 300 Anfragen, Budgetwarnung bei 20 €. Dann `cp /home/webklar/apps/.env /home/webklar/apps/.env.bak-$(date +%F)-workers-p6`, Zeile `WORKERS_PLACES_API_KEY=` in `/home/webklar/apps/.env` eintragen (den Wert sieht Claude nie). Prüfen: `command grep -c '^WORKERS_PLACES_API_KEY=.' /home/webklar/apps/.env` ⇒ `1`; `command grep -c 'WORKERS_PLACES_API_KEY' /home/webklar/apps/docker-compose.yml` ⇒ mindestens `1` (sonst nach Backup `docker-compose.yml.bak-$(date +%F)-workers-p6` unter `environment:` des Dienstes `workers` die Listenzeile `- WORKERS_PLACES_API_KEY=${WORKERS_PLACES_API_KEY:-}` ergänzen, in derselben Listenschreibweise wie die übrigen Zeilen dort; P1s Compose-Block enthält sie schon, die Prüfung ist also meist grün); `docker compose -f /home/webklar/apps/docker-compose.yml config -q` ⇒ keine Ausgabe, Exit 0. -3. **Dienst ausrollen.** `cd /home/webklar/apps/workers-dev && git push origin p6-ohne-website:main` (Gitea `knso/workers-dienst`), dann `git -C /home/webklar/apps/workers pull --ff-only && cd /home/webklar/apps && docker compose build workers && docker compose up -d --no-deps workers`. Prüfen: `docker logs --tail 20 webklar-workers` ohne Fehler; `docker exec webklar-workers wget -qO- http://localhost:8092/api/workers/health` ⇒ `{"ok":true,…}`. (Die Rollen-Prompts `prompts/*.md`, darunter `bea.md` und `max.md`, liegen im Checkout und werden ohne Neubau neu gelesen: Änderungen an ihnen brauchen nur `git -C /home/webklar/apps/workers pull --ff-only`, H5.) -4. **Schema prüfen.** P6 braucht kein neues Schema (E1/E2). `docker exec webklar-workers node scripts/setup-collections.mjs --check` ⇒ Exit 0; sonst fehlt etwas aus P3 oder P4 (`privacyInteressentenLive`, Index auf `workersMessages.threadId`): ohne `--check` ausführen und erneut prüfen. -5. **Live-Probe (manuell, kostet 0,035 $: eine Places-Anfrage, kein Sprachmodell).** `docker exec webklar-workers node scripts/probe-scout.mjs --room handwerk --query "Elektriker in Kaiserslautern" --max 3`. Erwartet: `Places: 20 Treffer, davon mit Website: …`; je Betrieb eine Zeile mit `status=ok` (oder `robots`, `nicht_lesbar`), `Mail=gefunden` oder `keine`, `Quelle=`, `Telefon=ja|nein`, `MX=ok|nein|-`; `Kosten gesamt: 0,035 $`. Bei `status=robots` oder `nicht_lesbar` für viele Betriebe: Probe mit einer anderen Anfrage wiederholen, sonst melden. (Ob Haiku die Websuche kann, betrifft nur Rita und Finn und wird in P1/P3 geprüft, nicht hier.) -6. **Datenschutztext live.** (a) Kenso gibt den Text aus P6-T20 frei und nennt den Mail-Anbieter. (b) Der Ausführende committet in `dev-workers-datenschutz`; Kenso pusht: `git -C /home/webklar/apps/kundenbereich.webklar.com/dev-workers-datenschutz push origin feature/workers-datenschutz:main`; nach 1–2 Minuten `curl -s https://project.webklar.com/datenschutz.html | command grep -c 'id="interessenten"'` ⇒ `1`. (c) Website: `git -C /home/webklar/apps/webklar.com/dev-workers-ds push origin fix/datenschutz-anker:main`; deployt der Webhook nicht binnen 3 Minuten: `docker exec webklar-webclaw-build sh -c 'DEPLOY_TRIGGER=manual sh "${WEBCLAW_DEPLOY_SCRIPT:-/deploy.sh}"'`. Prüfen im Browser: `https://www.webklar.com/datenschutz#interessenten` landet auf `https://project.webklar.com/datenschutz.html#interessenten` und zeigt „11. Interessenten“. (d) Rechtliches: Kenso erzeugt in der Appwrite-Konsole den kurzlebigen Schlüssel „Rechtliches-Upload“ (files.read, files.write, documents.read, documents.write, Ablauf 1 Tag) und legt ihn nach `/root/.appwrite-rechtliches.key` (chmod 600); der Ausführende (Claude) führt im Portal-Worktree `timeout 300 node --max-old-space-size=1024 scripts/datenschutz-hochladen.mjs --probe` und dann `timeout 300 node --max-old-space-size=1024 scripts/datenschutz-hochladen.mjs --freigabe=` aus; Prüfen in WOMS „Rechtliches“: Karte „Datenschutzerklärung“ zeigt die neue Version als „aktuell“, die alte als „abgelöst“; danach löscht Kenso den Schlüssel in der Konsole. -7. **Schalter umlegen (erst jetzt dürfen Kaltmails raus).** `docker exec webklar-workers node scripts/p6-freischalten.mjs datenschutz --check` ⇒ „Seite ok“; dann `… datenschutz --set` ⇒ „Schalter privacyInteressentenLive: an“ (derselbe Schalter wie P3s `datenschutz-schalter.mjs --an`, aber erst nach der Seitenprüfung; P3s Schritt „Clara einschalten“ ist damit mit erledigt). -8. **Räume verbinden, einen nach dem anderen.** `docker exec webklar-workers node scripts/p6-freischalten.mjs raeume --set handwerk` ⇒ „Verbundene Räume: …handwerk“. Ab der nächsten Nacht (22:00 Suche, 05:30 Auswahl, ab 09:00 Versand) arbeitet Hanna; weitere Räume erst, wenn Zustellung und Antworten gut aussehen (`aerzte`, `selbst`, `laden` mit demselben Befehl). -9. **WOMS.** `git -C /home/webklar/apps/ticket.webklar.com/dev-workers push origin feature/workers:test` (wenn P2/P3 mit dran sind, laut Übersicht §1.1). Prüfen: `curl -s -o /dev/null -w '%{http_code}' https://ticket.webklar.com/api/workers/botleads` ⇒ `401`; in WOMS `/hustler` ⇒ Reiter „Bot-Leads“. -10. **Erste Nacht beobachten.** Morgens: Morgenbericht-Mail „Hustler-Nachtbericht TT.MM.JJJJ“ (06:30); „Bot-Leads“ zeigt rund 7 Einträge mit Grund; im Absender-Postfach „Gesendet“ höchstens 3 Mails, alle zwischen 09:00 und 16:00, jede mit genau einem Link im Text (`https://www.webklar.com`), Fußblock und Abmelde-Link (Klick testen: erscheint der Eintrag in der Sperrliste?); `workersDaily` des Tages zeigt `mailsCold` ≤ 3. Beschwerden und Antworten landen wie in P4 beschrieben im Pult. Zeigt das Board „Versand gestoppt“ (Bounce-Notbremse, H4), sind harte Bounces aufgetreten: Ursache prüfen (falsche Adressen? Absender-Reputation?), dann gibt ein Admin im Einstellungs-Dialog „Versand wieder freigeben“; die Erstmail-Aufträge blieben solange offen. Verweigert das Tor eine Kaltmail aus P6 (Pult „Mail nicht gesendet: …“, z. B. `text_zu_lang` bei einem sehr langen Angebotssatz), gehört der Entwurf geprüft, nicht das Tor gelockert. -11. **Not-Aus und Rückbau.** Sofort: Pausenschalter in WOMS. Nur Kaltmail aus (stoppt auch Claras Erstantworten, P3): `docker exec webklar-workers node scripts/p6-freischalten.mjs datenschutz --off`. Suche aus: `… raeume --clear`. Beides ist ohne Neustart wirksam. Die Bounce-Notbremse (H4) stoppt den Versand außerdem von selbst, siehe Schritt 10. +Was P6 tut: Jede Nacht um 22:00 schickt Otto einen der vier Scouts los (Ali: Ärzte & Praxen, Hanna: Handwerk, Sven: Selbstständige, Lea: Läden & Gastro). Der Scout holt bei Google Places bis zu 20 Treffer je Anfrage (höchstens drei Anfragen pro Lauf), liest die eigene Website jedes neuen Betriebs (Impressum und Kontaktseite, ohne KI), ergänzt aus OpenStreetMap, und Bea bewertet die Kandidaten. Um 05:30 wählt Bea die Top 3 (so viele, wie am Sendetag Mail-Plätze frei sind), Max schreibt jedem eine Kaltmail, sie geht Montag bis Freitag zwischen 09:00 und 16:00 hinaus; nach etwa sieben Tagen folgt einmal eine Erinnerung. Alle anderen Kandidaten stehen mit Grund im Reiter „Bot-Leads“. Um 03:00 kürzt Arne abgelaufene Daten, um 03:40 löscht der Dienst alte Antworten im Antwort-Postfach. **Achtung:** Das sind echte Werbemails an Fremde und echte Kosten bei Google, Anthropic und Resend. Eine Mail geht nur an eine Adresse, die im Impressum oder auf der Kontaktseite des Betriebs steht; ein Betrieb ganz ohne Website kann also nie eine Mail bekommen, die Mails gehen fast nur an veraltete Websites. Erwartete Kosten: unter 0,3 $ pro Nacht (F6). +### TEIL A: Datenschutztext live (unabhängig vom Dienst) + +Ziel: Der Abschnitt 11 „Interessenten“ steht auf `https://project.webklar.com/datenschutz.html` (Anker `#interessenten`), `webklar.com/datenschutz#interessenten` führt dorthin (jede Mail verlinkt `https://www.webklar.com/datenschutz#interessenten`), und die Fassung liegt in WOMS → Rechtliches. Der Text liegt im Portal-Worktree `/home/webklar/apps/kundenbereich.webklar.com/dev-workers-datenschutz` (Zweig `feature/workers-datenschutz`), die Weiterleitung im Website-Worktree `/home/webklar/apps/webklar.com/dev-workers-ds` (Zweig `fix/datenschutz-anker`). Mach Stand, Portal-Push und Upload am selben Tag. + +**1. Vorbedingungen (du, vor Schritt 5). Kein Befehl.** (a) Lies die Nutzungsbedingungen und die Acceptable Use Policy von Resend: Ist die erste geschäftliche E-Mail an ein Unternehmen (B2B, höchstens drei pro Tag) erlaubt? Schreib Claude eine Zeile, „erlaubt“ oder „Risiko einer Kontosperre bewusst getragen“; Claude hält sie in der Notiz zu P6 fest. (b) Nimm im Resend-Konto und im Cloudflare-Konto den Auftragsverarbeitungsvertrag (mit Standardvertragsklauseln) an und leg ihn ab; der Text verspricht beides. (c) Frag die Kanzlei: Bot-Leads (Betriebe, die wir nicht anschreiben, aber bis zu 90 Tage mit Namen speichern) erfahren davon nur über diesen öffentlichen Text (Art. 14 Abs. 5 lit. b DSGVO, unverhältnismäßiger Aufwand), und ob die Nutzungsbedingungen der Google Maps Platform erlauben, was die Scouts tun (gespeichert wird nur die Place-ID, dazu die selbst gelesene Website-Adresse). (d) In WOMS → Hustler → Einstellungen bleiben „Nachfassen nach (Tage)“ bei 7, „Aufbewahrung (Tage)“ bei höchstens 90 und „Unverlangte Mails pro Tag“ bei höchstens 3; der Text verspricht genau das. *Erwartet:* (a), (b) und (d) erledigt. *Wenn nicht:* (a) oder (b) offen = Schritt 5 nicht ausführen; (c) offen = Teil A trotzdem fertigstellen, aber Block E in Teil B erst nach der Antwort. + +**2. Text lesen und freigeben (du).** Zeigt dir die Abschnitte 11, 12 und 14 im Klartext: +``` +cd /home/webklar/apps/kundenbereich.webklar.com/dev-workers-datenschutz && sed -n '//,/

13\./p;/

14\./,/

15\./p' public/datenschutz.html | sed 's/<[^>]*>//g' | grep -v '^[[:space:]]*$' | fold -s -w 110 +``` +Prüfe diese Aussagen: +- **Quellen:** Website des Betriebs (die E-Mail-Adresse nur aus Impressum oder Kontaktseite, nie geraten), OpenStreetMap, Google Maps (nur die Kennung des Eintrags, die Place-ID); Telefonnummern nur, wenn öffentlich genannt, bei Heilberufen nie. +- **Umfang:** höchstens zwei E-Mails je Betrieb (Angebot, Erinnerung nach etwa sieben Tagen), höchstens drei neue Betriebe pro Tag, KI-Hinweis in jeder Nachricht, Antworten sortiert ein KI-Assistent, Beschwerden bearbeitet ein Mitarbeiter. +- **Empfänger (Abschnitt 11 und 12):** Hetzner (Daten und Antwort-Postfach), Anthropic Ireland, Limited (auch öffentliche Website-Texte des Betriebs für die Bewertung; gegen die aktuellen Anthropic-Bedingungen prüfen), Resend, Inc. (Versand), Cloudflare, Inc. (Weiterleitung der Antworten), Overpass-API (Suche nach Name und Ort); Standardvertragsklauseln nennt der Text für Anthropic, Resend und Cloudflare; dazu „Abweichend von Abschnitt 8“ für diese Mails. +- **Fristen:** 90 Tage nach dem Fund, 30 Tage nach Absage oder Widerspruch, sechs Monate nach der letzten Nachricht (der Dienst kürzt etwas früher: nach 89, 29 und 180 Tagen); Antworten im Postfach spätestens 30 Tage nach Eingang, bei Widerspruch sofort (das machst du von Hand, G3); das Versandprotokoll löscht Resend nach eigener Frist. +- **Was nach dem Kürzen bleibt:** nur Hash-Werte (E-Mail, Domain, Telefon, Place-ID, Anzeigenkennung, Name mit Postleitzahl) sowie Ort, Branche, Punktzahl und Status ohne Namen und Kontaktdaten. +- **Rechte:** Information nach Art. 14 hier und in der ersten Nachricht, Widerspruch nach Art. 21 (Kasten mit Antwort, Abmelde-Link und support@webklar.com), Auskunft und weitere Rechte in Abschnitt 13, keine automatisierte Einzelentscheidung nach Art. 22 (Abschnitt 14: die Bewertung entscheidet nur, ob und wann wir schreiben). +- **Fragen an dich:** die Kanzleifrage aus Schritt 1 (c); der Fuß jeder Kaltmail sagt „Wir nutzen sie, um Ihnen einmalig ein Angebot zu machen“, der Text nennt Angebot und Erinnerung (der Fuß steht in `src/mail/coldFooter.js`, nicht im Text): passt das so? + +Das Prüf-Skript verlangt später im Abschnitt 11 wörtlich diese Wendungen: „Art. 6 Abs. 1 lit. f DSGVO“, „spätestens 90 Tage nach dem Fund“, „innerhalb von 30 Tagen“, „sechs Monate nach der letzten Nachricht“, „Anthropic Ireland, Limited“, „Ihr Widerspruchsrecht gegen diese Werbung (Art. 21 DSGVO)“, „höchstens zwei E-Mails“, „höchstens drei Betriebe pro Tag“, „KI-Assistent im Auftrag von WEBklar“, „Resend, Inc.“, „Cloudflare, Inc.“, „Hetzner Online GmbH“, „spätestens 30 Tage nach Eingang“, „Place-ID“, „bei Heilberufen nie“, „Hash-Werte“ und „Abweichend von Abschnitt 8“. Ändert dein Wortlaut eine davon, sag es Claude: er zieht die Liste in `scripts/p6-freischalten.mjs` und den Test `legal_datenschutz_interessenten` mit, sonst bleibt der Schalter in E3 zu. *Erwartet:* du gibst frei oder nennst Änderungen (Claude ändert nur `public/datenschutz.html` und `test/legalTexts.test.js`). *Wenn nicht:* nicht pushen, bevor du freigegeben hast. + +**3. [Claude] Stand setzen.** Der Stand ist der Tag des Portal-Pushs und des Uploads. Ist das der 20.10.2026 (der Stand im Text), tut Claude nichts. Sonst ändert er genau eine Zeile (Beispiel 22.10.2026) und druckt in Schritt 4 das PDF neu: +``` +cd /home/webklar/apps/kundenbereich.webklar.com/dev-workers-datenschutz && sed -i 's|

Stand [0-9.]*

|

Stand 22.10.2026

|' public/datenschutz.html && command grep -c '

Stand 22.10.2026

' public/datenschutz.html +``` +*Erwartet:* `1`. *Wenn nicht:* `0` = die Zeile hat ein anderes Format, Claude liest die Datei. + +**4. [Claude] Portal vorbereiten.** Zuerst den Stand von `origin/main`: +``` +cd /home/webklar/apps/kundenbereich.webklar.com/dev-workers-datenschutz && git status -sb | head -n3 && git fetch origin && git log --oneline HEAD..origin/main +``` +*Erwartet:* die Zeile `## feature/workers-datenschutz...origin/main [ahead N]` (nach Schritt 3 darunter ` M public/datenschutz.html`) und eine leere Liste. Ist die Liste nicht leer: `git merge origin/main` (nie `--force`, nie `rebase`); bei einem Konflikt in `public/datenschutz.html` beide Änderungen behalten und das spätere Stand-Datum nehmen; PDF und `.sha256` nie zusammenführen, eine Seite wählen und neu drucken (`FORCE=1` vor den Befehl). Dann das PDF (es druckt nur, wenn der Text sich seit dem letzten Druck geändert hat): +``` +free -m | sed -n 2p && flock -w 3600 /run/lock/hustler-schwer.lock systemd-run --scope -q -p MemoryMax=700M -p MemorySwapMax=0 nice scripts/build-legal-pdfs.sh datenschutz +``` +*Erwartet:* „available“ mindestens 2500, danach `-> docs/WEBklar-Datenschutzerklaerung.pdf` (neu gedruckt) oder `= docs/WEBklar-Datenschutzerklaerung.pdf (unveraendert)` (das PDF passt schon zum Text). Dann die Rechtstext-Tests: +``` +flock -w 3600 /run/lock/hustler-schwer.lock timeout 900 node --max-old-space-size=1024 --test --test-concurrency=2 test/legalTexts.test.js test/legalDocs.test.js test/buildLegalHtml.test.js test/legalV6Build.test.js test/analyseLegal.test.js test/shopTabRechtliches.test.js test/datenschutzHochladen.test.js test/rechtlichesHochladen.test.js +``` +*Erwartet:* `# fail 0`. Zuletzt, falls sich etwas geändert hat, committen (danach `git status --short` leer): +``` +git add public/datenschutz.html docs/WEBklar-Datenschutzerklaerung.pdf docs/WEBklar-Datenschutzerklaerung.pdf.sha256 && git commit -m "Datenschutz: Stand und Zusammenführung" -m "Co-Authored-By: Claude Sonnet 5.5 " +``` +*Wenn nicht:* ein roter Test oder `available` unter 2500 = nicht pushen; bei Speichermangel 60 Sekunden warten und wiederholen. + +**5. Portal pushen (du).** Das Portal ist beim Deploy 1 bis 2 Minuten nicht erreichbar (der Start installiert Pakete): push außerhalb der Geschäftszeit und stimm mit P5 A8 ab, wer zuerst pusht (P5 führt in seinem C1 ohnehin den dann aktuellen `origin/main` ein). +``` +git -C /home/webklar/apps/kundenbereich.webklar.com/dev-workers-datenschutz push origin feature/workers-datenschutz:main +``` +*Erwartet:* Push angenommen. *Wenn nicht:* „non-fast-forward“ = `main` hat neue Commits: nie erzwingen, Claude wiederholt Schritt 4. + +**6. Portal-Seite und PDF prüfen (du).** Nach 1 bis 2 Minuten: +``` +curl -s https://project.webklar.com/datenschutz.html | command grep -o 'id="interessenten"\|

Stand [0-9.]*' +``` +*Erwartet:* zwei Zeilen, erst `

Stand TT.MM.JJJJ` (dein Stand), dann `id="interessenten"`. Dazu legt dir Claude das PDF `docs/WEBklar-Datenschutzerklaerung.pdf` als Datei vor: oben derselbe Stand, und „11. Interessenten“, „Resend“ und „Cloudflare“ kommen vor (auf dem Host gibt es kein `pdftotext`). *Wenn nicht:* keine Zeile oder noch der alte Stand = 2 Minuten warten, dann Claude (Deploy-Hook). + +**7. [Claude] Website vorbereiten.** +``` +cd /home/webklar/apps/webklar.com/dev-workers-ds && git status -sb | head -n3 && git fetch origin && git log --oneline HEAD..origin/main +``` +*Erwartet:* `## fix/datenschutz-anker...origin/main [ahead 1]` und eine leere Liste (sonst `git merge origin/main`; hat der Merge Dateien unter `src/` geändert, danach zusätzlich einmal alle Tests mit demselben Befehl ohne Dateinamen). Der Test (`node_modules` zeigt auf den Live-Baum: kein Cache schreiben): +``` +M=$(mktemp) && flock -w 3600 /run/lock/hustler-schwer.lock timeout 600 node --max-old-space-size=1024 node_modules/vitest/vitest.mjs run --no-cache --maxWorkers=2 src/test/datenschutz-weiterleitung.test.tsx; find /home/webklar/apps/webklar.com/build/node_modules/.vite -newer "$M" 2>/dev/null | head -n1; rm -f "$M" +``` +*Erwartet:* `Tests 4 passed (4)` und keine Zeile von `find`. *Wenn nicht:* ein roter Test oder eine `find`-Zeile = nicht pushen, Claude. + +**8. Website pushen und im Browser prüfen (du).** +``` +git -C /home/webklar/apps/webklar.com/dev-workers-ds push origin fix/datenschutz-anker:main +``` +*Erwartet:* Push angenommen; der Deploy-Hook antwortet sofort mit 202, nach etwa 3 Minuten ist die Seite neu gebaut. Prüfe nur im Browser (curl sieht nur die Hülle der Seite, die Weiterleitung läuft in JavaScript): `https://www.webklar.com/datenschutz#interessenten` (leitet auf `webklar.com` um, der Anker bleibt) und `https://webklar.com/datenschutz#interessenten` müssen auf `https://project.webklar.com/datenschutz.html#interessenten` enden und bei „11. Interessenten“ stehen. Öffne dazu `https://www.webklar.com/impressum`: das Impressum muss erscheinen (jede Kaltmail verlinkt es im Fuß, und die Anbieterzeile der Mail muss ihm entsprechen). *Wenn nicht:* nach 3 Minuten noch die alte Weiterleitung (sie verliert den Anker) = Deploy von Hand anstoßen: +``` +docker exec webklar-webclaw-build sh -c 'DEPLOY_TRIGGER=manual sh "${WEBCLAW_DEPLOY_SCRIPT:-/deploy.sh}"' +``` + +**9. Weiterleitung auch ohne JavaScript (du, empfohlen).** Ohne sie scheitert der Link in jeder Mail für Mail-Scanner und Browser ohne JavaScript, und die curl-Prüfungen in P3 G2 und P4 G3/H1 (`curl -sL https://www.webklar.com/datenschutz | grep -Ec 'id=.interessenten'`) bleiben auf `0`. Ein Eintrag in der nginx-Datei der Website (Sicherung zuerst; die Datei wird an Ort und Stelle überschrieben, damit der Container sie sieht): +``` +cd /home/webklar/apps/webklar.com/nginx && B=webklar.com.conf.bak-$(date +%F)-workers-p6 && cp -p webklar.com.conf "$B" && sed '/^ # SPA Routing/i\ # Datenschutz liegt im Portal; der Anker (#interessenten aus der Kaltmail) bleibt bei der Weiterleitung erhalten\n location = /datenschutz { return 301 https://project.webklar.com/datenschutz.html; }\n' "$B" > webklar.com.conf && docker exec webklar-webclaw nginx -t && docker exec webklar-webclaw nginx -s reload +``` +*Erwartet:* `nginx -t` meldet `syntax is ok` und `test is successful`. Dann: +``` +curl -sI https://webklar.com/datenschutz | command grep -i '^HTTP\|^location'; curl -sL https://www.webklar.com/datenschutz | command grep -c 'id="interessenten"' +``` +*Erwartet:* `HTTP/2 301`, `location: https://project.webklar.com/datenschutz.html`, dann `1`; im Browser springt `https://www.webklar.com/datenschutz#interessenten` weiter zum Abschnitt (der Browser hängt den Anker an die 301 an). *Wenn nicht:* `nginx -t` rot = `cp -p webklar.com.conf.bak--workers-p6 webklar.com.conf` und Claude; ohne diesen Schritt ersetzt du die Prüfungen in P3 und P4 durch Schritt 6. + +**10. Upload nach WOMS → Rechtliches (du, oder Claude nach deinem „Go“).** Der Schlüssel: Appwrite-Konsole (Projekt `6a1058610003c5a13a05` → Integrationen → API-Schlüssel) → Schlüssel `Rechtliches-Upload`, Ablauf 1 Tag, Bereiche `files.read`, `files.write`, `documents.read`, `documents.write`. Dann ablegen (verdeckt eintippen): +``` +( read -rs -p 'Schlüssel: ' K; echo; umask 077; printf '%s' "$K" > /root/.appwrite-rechtliches.key ) +``` +Probe, nur lesend (der Stand im Ordner muss der gepushte sein): +``` +cd /home/webklar/apps/kundenbereich.webklar.com/dev-workers-datenschutz && git status -sb | head -n1 && git status --short -- public/datenschutz.html docs/WEBklar-Datenschutzerklaerung.pdf && timeout 300 node --max-old-space-size=1024 scripts/datenschutz-hochladen.mjs --probe +``` +*Erwartet:* die Statuszeile ohne `[ahead …]`, sonst keine Ausgabe von `git status`, eine Zeile `datenschutz: v1:current …` (die Versionsliste) und zuletzt `Schlüssel ok (files.read + documents.read), Karte datenschutz vorhanden.` *Wenn nicht:* `STOPP: Karte fehlt: datenschutz` = nichts hochladen, erst die Karte in WOMS → Rechtliches anlegen; 401 oder „missing scope“ = falscher Schlüssel. Dann hochladen, mit dem Tag aus dem Stand (der Stand in der HTML muss genau dieses Datum tragen): +``` +timeout 300 node --max-old-space-size=1024 scripts/datenschutz-hochladen.mjs --freigabe=JJJJ-MM-TT +``` +*Erwartet:* `+ datenschutz vN (2 Dateien), bisherige Version abgelöst`, Exit 0. *Wenn nicht:* `Abbruch: Stand … in public/datenschutz.html passt nicht zur Freigabe …` = Datum falsch gesetzt; `Abbruch: nicht committeter Stand …` = Claude. Ein zweiter Lauf meldet `= datenschutz: schon hochgeladen` und schreibt nichts; das beweist aber nicht, dass die alte Version abgelöst wurde, prüfe es in WOMS: Rechtliches → Unsere Rechtstexte → Karte zur Datenschutzerklärung zeigt `vN · gilt seit TT.MM.JJJJ`; im „Verlauf“ trägt genau eine Version „gültig“, die bisherige einen Zeitraum bis zum Vortag, der Vermerk lautet „Fassung vom TT.MM.JJJJ: Abschnitt 11 Interessenten (Ansprache per E-Mail)“, und der PDF-Link zeigt denselben Stand wie Schritt 6. Stehen zwei Versionen auf „gültig“, setzt Claude in der Appwrite-Konsole (Sammlung `legalVersions`) die alte auf `superseded` mit `validTo` am Vortag. **Danach den Schlüssel in der Konsole löschen** und die Datei: +``` +rm -f /root/.appwrite-rechtliches.key +``` + +**Fertig, wenn (Teil A):** Schritt 6, 8 und 10 ihre „Erwartet“-Zeilen erfüllen. Jetzt darfst du P3 Block G und P4 Block H machen; ihre curl-Prüfung auf `www.webklar.com/datenschutz` wird erst mit Schritt 9 grün, ohne ihn nimm stattdessen die Prüfung aus Schritt 6. + +### TEIL B: Scouts und Kaltmail + +Reihenfolge einhalten: Block A bis H. Die Kommentare in `scripts/p6-freischalten.mjs` und `scripts/probe-scout.mjs` verweisen noch auf die Nummern 5, 7, 8 und 11 einer früheren Fassung dieser Anleitung; gemeint sind die Probe (D2, D3), der Schalter (E1 bis E3), die Räume (E8) und der Not-Aus (H1). + +#### A. Voraussetzungen prüfen + +**A1. Teil A ist durch, P1 bis P4 laufen.** +``` +curl -s https://project.webklar.com/datenschutz.html | command grep -c 'id="interessenten"' && curl -s https://ticket.webklar.com/api/workers/health && echo && docker ps --filter name=webklar-workers --format '{{.Names}} {{.Status}}' +``` +*Erwartet:* `1`, `{"ok":true,"version":"0.1.0"}` und `webklar-workers Up …`. *Wenn nicht:* `0` = Teil A, Schritt 5 bis 10; keine Gesundheitsantwort = erst die Anleitungen von P1 bis P4 abarbeiten. + +**A2. Absender-Domain D, Resend und Cloudflare.** D ist eine eigene Domain nur für diese Mails, nie `webklar.com` und keine Subdomain davon (das Sende-Tor verweigert sie). +``` +D=$(docker exec webklar-workers printenv WORKERS_SMTP_FROM | sed 's/.*@//; s/[> ].*//') && docker exec webklar-workers sh -c 'printenv WORKERS_SMTP_HOST WORKERS_SMTP_PORT WORKERS_SMTP_USER' && echo "Domain: $D" && dig +short MX "$D" && dig +short TXT "$D" && dig +short TXT "resend._domainkey.$D" && dig +short MX "send.$D" && dig +short TXT "_dmarc.$D" +``` +*Erwartet:* `smtp.resend.com`, `465`, `resend`; `Domain: `; dreimal `route…mx.cloudflare.net`; eine TXT-Zeile `v=spf1 include:_spf.mx.cloudflare.net …`; eine TXT-Zeile mit `p=` (DKIM von Resend); eine MX-Zeile für `send.`; `v=DMARC1; p=none; …` ohne Berichtsadresse, die auf das Antwort-Postfach zeigt. Im Resend-Konto: Domain „Verified“, Region eu-west-1 (Irland), Open tracking und Click tracking aus (kein Tracking-Pixel, ein Klick-Tracking schriebe den einen erlaubten Link um), API-Schlüssel nur mit „Sending access“ für diese Domain; notiere dir die Aufbewahrungsdauer der gesendeten Mails in deinem Resend-Plan (der Datenschutztext sagt „nach seiner eigenen Aufbewahrungsfrist“). *Wenn nicht:* richte es so ein. (1) Domain als Zone in Cloudflare (Nameserver beim Registrar umstellen; Email Routing braucht Cloudflare-DNS). (2) Cloudflare → Email Routing: Ziel = das Hetzner-Antwort-Postfach (Bestätigungsmail anklicken), Regel `max@` an dieses Ziel, **keine** Catch-all-Regel; das Postfach darf nur Antworten des Hustlers bekommen (jede Mail ohne Faden wird ein Pult-Eintrag), Spam-Verschieben aus oder den Cloudflare-Absender freigeben. (3) Resend → Domains → `` hinzufügen (eu-west-1), die angezeigten Einträge unverändert übernehmen (DKIM-TXT `resend._domainkey`, MX und SPF-TXT auf `send.`), Tracking aus. (4) DNS `_dmarc.` TXT `v=DMARC1; p=none; rua=mailto:` (nach zwei Wochen ohne Probleme `p=quarantine`). (5) `.env`: Führe zuerst B1 aus (die Sicherungen; B1 lässt sich vorziehen und schadet beim zweiten Mal nicht). Dann ersetzt du die fünf SMTP-Zeilen (der Resend-Schlüssel ist ein anderes Geheimnis als das IMAP-Passwort; die IMAP-Zeilen aus P4 B2/B3 mit Hetzner-Postfach und Port 993 bleiben unberührt; `max@` durch deine Adresse ersetzen, den Schlüssel tippst du verdeckt): +``` +cd /home/webklar/apps && sed -i '/^WORKERS_SMTP_\(HOST\|PORT\|USER\|PASS\|FROM\)=/d' .env && printf 'WORKERS_SMTP_HOST=smtp.resend.com\nWORKERS_SMTP_PORT=465\nWORKERS_SMTP_USER=resend\nWORKERS_SMTP_FROM=%s\n' 'max@' >> .env && ( read -rs -p 'Resend-API-Schlüssel: ' K; echo; printf 'WORKERS_SMTP_PASS=%s\n' "$K" >> .env ) +``` +Danach Neustart wie oben (`cd /home/webklar/apps && docker compose up -d --no-deps workers`); die Zeilenzahlen prüft C3. + +**A3. Das Antwort-Postfach wird gelesen und darf löschen.** +``` +docker exec webklar-workers cat /data/imap-state.json +``` +*Erwartet:* ein Eintrag `"antworten":{"uidValidity":…,"lastUid":…}`. Dann schick von Gmail eine Mail an `max@`: sie liegt nach wenigen Minuten im Posteingang (nicht im Spam) des Hetzner-Postfachs, und nach 2 Minuten steht im Pult „Mail ohne Faden“ (erledige ihn). Lösch die Testmail danach im Webmail von Hand: so siehst du, dass dieses Konto in INBOX löschen darf (das braucht der tägliche Lauf um 03:40). **Achtung:** dieser Lauf löscht in der INBOX jede Mail, die der Dienst schon aufgenommen hat (alles bis zu seinem Lesestand, egal von wem und ob du sie gelesen hast) und die älter als 29 Tage ist, höchstens 500 am Tag; in der ersten Nacht trifft es auch den alten Bestand (Pilot-Mails, DMARC-Berichte, Testmails). Was du aufheben willst, verschiebst du vorher in einen anderen Ordner (andere Ordner fasst der Lauf nie an). *Wenn nicht:* kein `antworten` nach 5 Minuten oder keine Mail = P4 B10 und die Cloudflare-Regel aus A2; Löschen geht nicht = erst die Rechte des Kontos beim Anbieter klären. + +**A4. P5 ist live oder bewusst von Hand.** +``` +docker exec webklar-workers printenv WORKERS_KAUFLINK +``` +*Erwartet:* `ja` (P5 Block G ist durch) oder `nein` (bewusst von Hand: Theo legt bei einem Kaufwunsch keinen Link an, sondern den Pult-Eintrag „Kaufwunsch – Kauf-Link noch nicht aktiv“, den du von Hand beantwortest). *Wenn nicht:* `ja` ohne fertiges P5 = sofort `WORKERS_KAUFLINK=nein` in der `.env` und neu starten (P4 H4). + +**A5. Platz, Speicher, Abmelde-Adresse.** +``` +df -h / | tail -1 && free -m | sed -n 2p && curl -s -o /dev/null -w '%{http_code}\n' https://entwurf.project.webklar.com/abmelden/x +``` +*Erwartet:* mindestens 3 GB frei, „available“ mindestens 2000, dann `404`. *Wenn nicht:* bei Platz oder Speicher nicht bauen (frag Claude); `502` oder ein Zertifikatsfehler = die Vorschau-Route aus P4 Block C ist kaputt, sie trägt den Abmelde-Link jeder Mail. + +**A6. Kein Befehl.** (a) Anthropic-Console → Workspace „Hustler“ → Ausgabenlimit 150 $ pro Monat (P3 A5). (b) Der Startbestand des Registers ist eingespielt (P1 Schritt 9): Appwrite-Konsole → `workersKeys` hat Dokumente, damit niemand angeschrieben wird, den WEBklar schon kennt. (c) Du kannst in Google Cloud die Places API (New) aktivieren (C1). *Erwartet:* alles drei stimmt. *Wenn nicht:* nachholen, bevor du weitermachst. + +#### B. Code: Sicherung, Zweige, Tests, Push, Image + +**B1. Sicherungen, Rückfall-Image, ALT-Stand** (das Image **vor** dem Bauen sichern; `vor-p6` wird nie überschrieben). +``` +cd /home/webklar/apps && { [ -e .env.bak-$(date +%F)-workers-p6 ] || cp -p .env .env.bak-$(date +%F)-workers-p6; } && { [ -e docker-compose.yml.bak-$(date +%F)-workers-p6 ] || cp -p docker-compose.yml docker-compose.yml.bak-$(date +%F)-workers-p6; } && { docker image inspect webklar-workers:vor-p6 >/dev/null 2>&1 || docker tag webklar-workers:latest webklar-workers:vor-p6; } && git -C /home/webklar/apps/workers status -sb | head -n1 && { [ -s /home/webklar/apps/workers-alt-p6.txt ] || git -C /home/webklar/apps/workers rev-parse --short HEAD > /home/webklar/apps/workers-alt-p6.txt; } && cat /home/webklar/apps/workers-alt-p6.txt +``` +*Erwartet:* `## main...origin/main`, dann eine Nummer: das ist **ALT-DIENST**, der Stand vor P6 (sie steht in `/home/webklar/apps/workers-alt-p6.txt`, H2 liest sie von dort; ein zweiter Lauf am selben Tag überschreibt weder sie noch die Sicherungen von `.env` und Compose); `docker images webklar-workers` zeigt `latest` und `vor-p6`. *Wenn nicht:* `## HEAD (no branch)` = `git -C /home/webklar/apps/workers checkout main` und den Befehl wiederholen; das Image `vor-p6` löschst du nicht, solange P6 nicht bewährt ist. + +**B2. [Claude] Dienst-Zweig zusammenführen und testen** (Worktree `/home/webklar/apps/workers-dev-p6`, Zweig `p6-ohne-website`; nie `/home/webklar/apps/workers-dev`). +``` +cd /home/webklar/apps/workers-dev-p6 && git status -sb | head -n3 && git log --oneline p6-ohne-website..p4-gespraeche && git fetch origin && git merge-base --is-ancestor origin/main HEAD && echo FF-OK +``` +*Erwartet:* `## p6-ohne-website` (sauber), eine leere Liste, `FF-OK`. Ist die Liste nicht leer: `git merge p4-gespraeche` (auch spätere Korrekturen von P4 gehören hinein); fehlt `FF-OK`: `git merge origin/main`; nie `rebase`, nie `--force`. Dann der Gesamtlauf: +``` +free -m | sed -n 2p && flock -w 3600 /run/lock/hustler-schwer.lock timeout 900 node --max-old-space-size=1024 --test --test-concurrency=2 test/*.test.js +``` +*Erwartet:* „available“ mindestens 2500, `# fail 0` und `# skipped 0` (zuletzt 2855 Tests grün). *Wenn nicht:* nicht pushen; Claude behebt es. + +**B3. Pushen und Stand übernehmen** (der `pull` ändert auch `prompts/*.md`, die der laufende Dienst sofort liest). +``` +git -C /home/webklar/apps/workers-dev-p6 push origin p6-ohne-website:main && git -C /home/webklar/apps/workers pull --ff-only +``` +*Erwartet:* kein Fehler; `git -C /home/webklar/apps/workers log --oneline -1` und `git -C /home/webklar/apps/workers-dev-p6 log --oneline -1` zeigen dieselbe Nummer. [Claude] bestätigt danach, dass `grep -rn '\.sendMail(' /home/webklar/apps/workers/src` nur `src/mail/gate.js` und `src/notify.js` nennt (jede Mail an Interessenten geht durchs Sende-Tor). *Wenn nicht:* „non-fast-forward“ oder „Not possible to fast-forward“ = nie `--force`, nie `rebase`, Claude Bescheid. + +**B4. Image bauen** (der laufende Container bleibt bis C5 auf dem alten Image). +``` +cd /home/webklar/apps && free -m | sed -n 2p && docker compose build workers +``` +*Erwartet:* „available“ mindestens 1500, der Bau endet ohne Fehler, `docker images webklar-workers` zeigt `latest` mit neuer Kennung und `vor-p6` mit der alten. *Wenn nicht:* „Killed“ oder Speicherfehler = nichts erzwingen, A5 beachten. + +**B5. [Claude] WOMS-Zweig vorbereiten** (der Hustler braucht den Reiter „Bot-Leads“ und den Pausenschalter, bevor du schaltest; `feature/workers` ist gegenüber `origin/test` zurück, etwa um „Website-Statistik“). +``` +cd /home/webklar/apps/ticket.webklar.com/dev-workers && git status -sb | head -n3 && git fetch origin && git log --oneline HEAD..origin/test +``` +*Erwartet:* `## feature/workers...origin/test [ahead N]` und eine leere Liste. Hat `test` neue Commits, steht dort `[ahead N, behind M]` und die Liste nennt sie (etwa „Website-Statistik“): dann `git merge origin/test` (nie `--force`). Danach Suite und Bau, jeweils unter `flock`: +``` +flock -w 3600 /run/lock/hustler-schwer.lock timeout 900 node --max-old-space-size=1024 --test --test-concurrency=2 tests/*.test.mjs tests/unit/*.test.mjs +``` +``` +flock -w 3600 /run/lock/hustler-schwer.lock systemd-run --scope -p MemoryMax=1500M -p MemorySwapMax=0 --quiet timeout 600 npx vite build --outDir "$(mktemp -d "${TMPDIR:-/tmp}/p6w.XXXXXX")" +``` +*Erwartet:* `# fail 0`, Bau Exit 0, und `ls -d /home/webklar/apps/ticket.webklar.com/build/node_modules/.vite* 2>/dev/null` gibt nichts aus. *Wenn nicht:* nicht pushen, Claude. + +**B6. WOMS pushen (du).** +``` +git -C /home/webklar/apps/ticket.webklar.com/dev-workers push origin feature/workers:test +``` +*Erwartet:* Push angenommen; nach 1 bis 3 Minuten baut der Webhook (die Seite ansehen: hart neu laden, Strg/Cmd+Umschalt+R). *Wenn nicht:* „non-fast-forward“ = `test` hat neue Commits: nie erzwingen, B5 wiederholen. + +#### C. Konfiguration, Schema, Neustart + +**C1. Google-Places-Schlüssel.** Google Cloud Console: „Places API (New)“ aktivieren, Schlüssel anlegen, auf diese API und die Server-IP beschränken, das Tageskontingent für Text Search auf 300 Anfragen setzen, Budgetwarnung bei 20 € (ein Google-Budget deckelt nichts, die harte Grenze steht im Dienst). Dann (die Zeile heißt genau so, `src/config.js` liest sie): +``` +cd /home/webklar/apps && sed -i '/^WORKERS_PLACES_API_KEY=/d' .env && ( read -rs -p 'Google-Places-Schlüssel: ' K; echo; printf 'WORKERS_PLACES_API_KEY=%s\n' "$K" >> .env ) +``` +*Erwartet:* keine Ausgabe nach dem Einfügen. *Wenn nicht:* Tippfehler = den Befehl wiederholen (er löscht die alte Zeile zuerst; Sicherung aus B1). + +**C2. Compose-Zeile für das Löschen alter Antworten** (genau die Zeile aus `deploy/compose-workers.yml`; sie fehlt in einer Compose-Datei aus der Zeit vor P6, und ohne sie gälte im Dienst 30 statt 29 Tage und die Zusage „spätestens 30 Tage“ hielte nicht). Der Befehl tut nichts, wenn die Zeile schon steht. +``` +cd /home/webklar/apps && [ "$(command grep -c WORKERS_ANTWORTEN_LOESCHEN_TAGE docker-compose.yml)" = 0 ] && sed -i '/^ workers:$/,/^ [a-z0-9_-]*:$/{ +/^ - WORKERS_UNSUB_SECRET=/a\ + - WORKERS_ANTWORTEN_LOESCHEN_TAGE=${WORKERS_ANTWORTEN_LOESCHEN_TAGE:-29} +}' docker-compose.yml; sed -n '/^ workers:$/,/^ [a-z0-9_-]*:$/p' docker-compose.yml | command grep -cF 'WORKERS_ANTWORTEN_LOESCHEN_TAGE=${WORKERS_ANTWORTEN_LOESCHEN_TAGE:-29}' +``` +*Erwartet:* `1`. In die `.env` gehört nur etwas, wenn du einen anderen Wert willst: `WORKERS_ANTWORTEN_LOESCHEN_TAGE=<1 bis 29>`; mehr als 29 bricht die Zusage (der Lauf kommt einmal täglich), `0` schaltet das Löschen aus und widerspricht dem Datenschutztext: nie. *Wenn nicht:* `0` = der Block heißt anders oder ist anders eingerückt: Zeile von Hand unter `environment:` von `workers` einfügen; `2` = doppelt, die doppelte im Editor löschen (Sicherung aus B1). + +**C3. Konfiguration prüfen** (zählt nur Zeilen). +``` +cd /home/webklar/apps && command grep -c '^WORKERS_PLACES_API_KEY=.' .env; command grep -c '^WORKERS_UNSUB_SECRET=.' .env; command grep -c '^WORKERS_SMTP_FROM=.*[@.]webklar\.com' .env; command grep -Ec '^WORKERS_SMTP_(HOST|PORT|USER|PASS|FROM)=.' .env; command grep -Ec '^WORKERS_IMAP_(HOST|PORT|USER|PASS)=.' .env; docker compose config -q && echo KONFIG-OK +``` +*Erwartet:* `1`, `1`, `0`, `5`, `4` und `KONFIG-OK`. *Wenn nicht:* `0` bei der ersten Zahl = C1; `0` bei der zweiten = P4 B3 (das Abmelde-Geheimnis fehlt, jede Mail meldet `nicht_konfiguriert`); `1` bei der dritten = der Absender liegt auf `webklar.com` oder einer Subdomain davon, das Tor verweigert ihn (A2); unter `5` bei der vierten oder unter `4` bei der fünften = eine SMTP- oder IMAP-Zeile fehlt (der Dienst bricht sonst mit `Konfiguration unvollständig: ` ab), darüber = doppelte Zeilen aus früheren Versuchen, nur die doppelten im Editor löschen (Sicherung aus B1). + +**C4. Schema prüfen** (auf dem Host, **vor** dem Neustart; der Laufzeit-Schlüssel des Dienstes darf das nicht, also nie per `docker exec`). P6 ändert das Schema nicht (`src/schema.js` ist seit dem Stand `558ce24` von P4 und P5 unverändert); die Prüfung zeigt, ob P4 und P5 es vollständig angelegt haben. Lege in der Appwrite-Konsole einen Schlüssel `workers-setup` an: Ablauf 1 Tag, Bereiche `databases.read`, `collections.read/write`, `attributes.read/write`, `indexes.read/write`, nie `databases.write`, nicht in `.env`. +``` +cd /home/webklar/apps/workers-dev-p6 && ( read -rs -p 'Appwrite-Key workers-setup: ' APPWRITE_API_KEY; echo; export APPWRITE_API_KEY; timeout 300 node --max-old-space-size=1024 scripts/setup-collections.mjs --check; echo "Exit $?" ) +``` +*Erwartet:* 13 Zeilen `= workers…: N Attribute, M Indizes vorhanden` (zum Beispiel `= workersProspects: 31 Attribute, 5 Indizes vorhanden`), dann `Alles vorhanden.` und `Exit 0`. **Danach den Schlüssel in der Appwrite-Konsole löschen.** *Wenn nicht:* `FEHLT`-Zeilen mit `Exit 1` = ein früherer Schritt (P4 B6, P5 A3) fehlt: derselbe Befehl ohne `--check` zieht alles nach (wiederholbar), danach wieder mit `--check`; 401 oder „missing scope“ = falscher Schlüssel; `NICHT BEREIT` = ein paar Minuten warten. + +**C5. Neustart** (nur, wenn kein Bau läuft: im Hochhaus arbeiten weder Bob, Bianca, Ben noch Paul, sonst geht der bezahlte Bau verloren, siehe P4 I3). +``` +cd /home/webklar/apps && free -m | sed -n 2p && docker compose up -d --no-deps workers +``` +*Erwartet:* „available“ mindestens 1500, der Container wird neu erstellt. *Wenn nicht:* Speichermangel = A5, nichts erzwingen. + +**C6. Läuft der neue Dienst?** Drei Prüfungen, jede einzeln. +``` +docker logs --tail 30 webklar-workers +``` +*Erwartet:* Zeilen mit `workers listening on 8092` und `vorschau listening on 8093`, keine mit `Abbruch:` oder `Konfiguration`. +``` +docker exec webklar-workers sh -c 'printenv WORKERS_ANTWORTEN_LOESCHEN_TAGE; printenv WORKERS_PLACES_API_KEY | wc -c; printenv WORKERS_SMTP_HOST' +``` +*Erwartet:* `29` (oder dein Wert), eine Zahl über 1 (der Schlüssel selbst wird nicht ausgegeben), `smtp.resend.com`. Das ist der Stand im laufenden Container. Nach 2 bis 3 Minuten: +``` +docker logs --since 5m webklar-workers 2>&1 | command grep -iE 'invalid query|index not found|attribute not found|unknown attribute|Tick abgebrochen|Unbehandelte|Fehler bei|Anmeldung abgelehnt' +``` +*Erwartet:* keine Ausgabe; `docker exec webklar-workers cat /data/imap-state.json` zeigt wieder `antworten` (bis zum ersten erfolgreichen Lesen wartet jede Kaltmail mit dem Grund `posteingang`). *Wenn nicht:* eine `Invalid query`- oder `Index`-Zeile = C4 ist nicht vollständig durchgelaufen; `Anmeldung abgelehnt` = Postfach-Zugang (nicht in einer Schleife wiederholen, P4 B10); `Abbruch:` oder `Konfiguration ungültig` = eine Zeile in der `.env` oder in der Compose-Datei ist kaputt (der Dienst startet sonst in einer Schleife neu): Zeile an Claude, nichts raten. Das Rückfall-Image liegt bereit (H2). + +**C7. WOMS-Abnahme.** Erst ohne Anmeldung: +``` +curl -s -o /dev/null -w '%{http_code}\n' https://ticket.webklar.com/api/workers/botleads +``` +*Erwartet:* `401`. *Wenn nicht:* `404` = der alte Dienst läuft noch (C5 wiederholen); `502` = der Dienst läuft nicht (C6). Dann eingeloggt auf `https://ticket.webklar.com` in der Entwicklerkonsole des Browsers: +```js +const j = await (await fetch('/v1/account/jwts', { method: 'POST', credentials: 'include', headers: { 'X-Appwrite-Project': '6a1058610003c5a13a05' } })).json() +for (const p of ['state', 'botleads']) { const r = await fetch('/api/workers/' + p, { headers: { 'X-Appwrite-JWT': j.jwt } }); console.log(p, r.status, await r.json()) } +``` +*Erwartet:* `state 200 {…}` und `botleads 200 {items: [], total: 0, pageSize: 50}`. In WOMS → Hustler gibt es die Reiter „Hochhaus“, „Freigabe“ und „Bot-Leads“; schalte „Alles anhalten“ → „Anhalten“ einmal ein (Zeile „Angehalten: Es wird nichts gesendet und nichts gebaut.“) und mit „Fortsetzen“ wieder aus. *Wenn nicht:* `state` mit einem Fehler (500) = Appwrite lehnt die neue Zählabfrage auf `purgeAfter` ab, meist fehlt Attribut oder Index: C4 ohne `--check` wiederholen, dann Claude; `botleads 404` = der alte Dienst läuft noch (C5); fehlt der Reiter, ist WOMS nicht ausgeliefert (B6, 3 Minuten warten, hart neu laden). + +#### D. Live-Probe (kostet nichts außer einer Places-Anfrage je Aufruf, keine Mail an Fremde) + +**D1. DNS im Container** (liefert der Docker-Resolver für fehlende AAAA-Einträge einen Fehler, gilt jede Website als unlesbar und jeder Kandidat endet als Bot-Lead). +``` +docker exec webklar-workers node --input-type=module -e "const d = await import('node:dns/promises'); for (const h of ['example.org', 'webklar.com']) for (const t of ['resolve4', 'resolve6']) { try { console.log(h, t, (await d[t](h)).length, 'Adressen') } catch (e) { console.log(h, t, e.code) } }" +``` +*Erwartet:* je Zeile eine Zahl von Adressen oder `ENODATA` bzw. `ENOTFOUND` (kein Eintrag). *Wenn nicht:* `ESERVFAIL`, `ETIMEOUT` oder `ECONNREFUSED` = der Resolver des Containers ist das Problem, nicht der Code: Claude, die Räume bleiben unverbunden. + +**D2. Probe Handwerk** (eine Places-Anfrage, 0,035 $; liest bis zu 5 Websites; schreibt nichts, ruft kein Sprachmodell auf; gibt nur Rechnernamen aus, nie Adressen, Telefonnummern oder Namen). +``` +timeout 300 docker exec webklar-workers node scripts/probe-scout.mjs --room handwerk --query 'Elektriker in Kaiserslautern' --max 5 +``` +*Erwartet:* `Places: N Treffer, davon mit Website: K`, bis zu fünf Zeilen ` status=ok|robots|nicht_lesbar|keine_website Mail=gefunden|keine Quelle= Telefon=ja|nein MX=ok|nein|-`, zuletzt `Kosten gesamt: 0,035 $`, Exit 0. Gut ist, wenn mindestens 60 % der Zeilen `status=ok` haben. *Wenn nicht:* `Fehler: Places-Anfrage fehlgeschlagen (HTTP 403).` = Schlüssel, API oder IP-Beschränkung (C1); `Abbruch: WORKERS_PLACES_API_KEY fehlt` = C1 und C5; fast nur `nicht_lesbar` = D1. + +**D3. Probe Läden und Gastro** (anderes Abfragemuster; im Raum `aerzte` zeigt `Telefon=ja`, was die Seite nennt, der Dienst speichert dort nie eine Nummer). +``` +timeout 300 docker exec webklar-workers node scripts/probe-scout.mjs --room laden --query 'Bäckerei in Mainz' --max 5 +``` +*Erwartet:* wie D2. Zusammen mit D2 gilt: mindestens 60 % `status=ok`, mindestens 2 der 10 Betriebe mit `Mail=gefunden` und `MX=ok`, weder Nummer noch Adresse noch Name in der Ausgabe. *Wenn nicht:* weniger = Räume nicht verbinden, die Zeilen (nur Rechnernamen) an Claude: er prüft, ob die Impressum-Erkennung zu viel abschneidet. + +**D4. Mail-Weg über Resend** (genau ein Versuch an deine eigene Adresse, ohne Schutz des Sende-Tors). +``` +docker exec webklar-workers node scripts/p4-livecheck.mjs smtp --to kenso.gri@gmail.com --ja-echt +``` +*Erwartet:* `Testmail gesendet (ein Versuch). Message-ID: <…>`, Exit 0. In Gmail „Original anzeigen“: SPF, DKIM und DMARC `PASS`; `List-Unsubscribe` und `List-Unsubscribe-Post: List-Unsubscribe=One-Click` stehen da; die Message-ID bleibt `<32 Hexzeichen.Id@D>` (ersetzt Resend sie, ordnet der Dienst Antworten nur noch über den Absender zu: sag Claude Bescheid); kein Tracking-Link. Empfohlen: derselbe Befehl einmal mit der Adresse von mail-tester.com, Ergebnis mindestens 9 von 10. Antworte auf die Testmail aus Gmail: sie liegt im Posteingang des Hetzner-Postfachs (Cloudflare-Weg) und `docker exec webklar-workers node scripts/p4-livecheck.mjs imap --ja-echt` zeigt ein steigendes `exists`. *Wenn nicht:* `SMTP-Login abgelehnt (…)` = **nicht wiederholen** (der Anbieter sperrt sonst das Konto): Schlüssel in der `.env` prüfen (Sicherung B1), neu starten, dann einmal erneut; ein PASS fehlt oder die Mail liegt im Spam = DNS aus A2, Claude. + +#### E. Freischalten + +**E1. Stand des Schalters und offene Mail-Aufträge.** +``` +docker exec webklar-workers node scripts/datenschutz-schalter.mjs --status +``` +*Erwartet:* `Datenschutz-Schalter: aus` oder `…: an`; schreib das Wort auf (**Stand vorher: ____**). Appwrite-Konsole → `woms-database` → `workersJobs`, Filter Status = `offen`: kein Auftrag der Art `antwort`, `vorschau_link`, `erinnerung`, `nachfass` oder `erstmail`. *Wenn nicht:* gibt es solche Aufträge, nicht einschalten: sie gingen sofort hinaus; Claude fragen. + +**E2. Live-Seite prüfen** (schreibt nie). +``` +docker exec webklar-workers node scripts/p6-freischalten.mjs datenschutz --check +``` +*Erwartet:* `Seite ok`, Exit 0 (Anker `#interessenten` da, die Wendungen aus Teil A Schritt 2 stehen zwischen dem Anker und „12. Empfänger“). *Wenn nicht:* Zeilen `Fehlt: …` und `Seite unvollständig.` (Exit 1) = der Text ist noch nicht live oder ein Wortlaut wurde geändert (Teil A, Schritt 2); `Veraltet: Google Workspace` (oder Migadu) = ein alter Stand ist live; `Seite nicht abrufbar: HTTP 404` = Teil A, Schritt 6. Den Schalter nicht setzen. + +**E3. Schalter an.** Das ist derselbe Schalter wie bei Clara (P3 Block G, P4 H2): ab jetzt dürfen Erstantworten und Kaltmails hinaus. +``` +docker exec webklar-workers node scripts/p6-freischalten.mjs datenschutz --set +``` +*Erwartet:* `Seite ok` und `Schalter privacyInteressentenLive: an`. Das Auswahl-Tick um 05:30 läuft einmal am Tag: schaltest du nach 05:30 ein, wählt Bea erst am nächsten Morgen. *Wenn nicht:* wie E2, dann bleibt der Schalter, wie er war (`Schalter bleibt unverändert.`). + +**E4. Zwei Probe-Betriebe an dich selbst** (Mo bis Do am Nachmittag; sie laufen durch das echte Tor und kosten zwei Sonnet-Entwürfe, wenige Cent). Der Befehl legt zwei Betriebe „Testbetrieb Muster“ an, Status „bewertet“, mit zwei Plus-Adressen von dir: +``` +docker exec -i webklar-workers node --input-type=module <<'JS' +import { loadConfig } from './src/config.js' +import { createDb } from './src/db.js' +const db = createDb(loadConfig().appwrite) +const jetzt = new Date() +for (const [nr, punkte] of [[1, 90], [2, 88]]) { + const id = `kalt${nr}${jetzt.getTime().toString(36)}` + await db.create('workersProspects', { + entrance: 'ohne_website', room: 'handwerk', foundBy: 'hanna', source: 'places', + sourceUrl: 'https://www.webklar.com/impressum', foundAt: jetzt.toISOString(), + name: 'Testbetrieb Muster', email: `kenso.gri+kalt${nr}@gmail.com`, emailSource: 'https://www.webklar.com/impressum', + city: 'Kaiserslautern', score: punkte, status: 'bewertet', isHealth: false, + purgeAfter: new Date(jetzt.getTime() + 3 * 86400000).toISOString(), updatedAt: jetzt.toISOString() + }, id) + console.log('Probe-Betrieb angelegt:', id) +} +JS +``` +*Erwartet:* zwei Zeilen `Probe-Betrieb angelegt: kalt1…` und `kalt2…`. Am nächsten Morgen um 05:30 wählt Bea beide (im Hochhaus bei Bea: `Platz 1 von 2: Punktzahl 90` und `Platz 2 von 2: Punktzahl 88`), Max schreibt `Erstmail entworfen (Kaiserslautern)`, und ab 09:00 (Werktag) kommen beide Mails bei dir an (`Erstmail gesendet`). *Wenn nicht:* keine Mail bis 16:00 = Wochentag und Uhrzeit, Pause in WOMS, Schalter (E1 bis E3) und `docker logs --since 24h webklar-workers 2>&1 | command grep -iE 'Sende-Tor|Top-3-Auswahl|Fehler bei'` an Claude. + +**E5. Die erste echte Kaltmail lesen (an `kalt1`).** Kein Befehl. Lies sie wie ein Fremder und prüfe: Absender `Max, KI-Assistent von WEBklar` von deiner Domain D (nie `webklar.com`), Anrede „Guten Tag,“, als erster Satz „Ich bin ein KI-Assistent von WEBklar und schreibe Ihnen im Auftrag von Kenso Grimm und Justin Klein.“, danach ein kurzer persönlicher Teil mit Name oder Ort, der Angebotssatz wörtlich wie in WOMS → Hustler → Einstellungen → Gruppe „Angebot“ → „Angebotstext“, die Zeile „Wenn Sie die Vorschau sehen möchten, antworten Sie einfach mit „Ja“.“, genau ein Link im Text (`https://www.webklar.com`), der Gruß „KI-Assistent von WEBklar, im Auftrag von Kenso Grimm und Justin Klein“ und der Fuß „Hinweise zu dieser Nachricht:“ mit genau vier Adressen (Impressum, Quelle der Adresse, Datenschutz mit `#interessenten`, Abmelden). Die Anbieterzeile im Fuß (GbR, Namen, Anschrift, Telefon, E-Mail) muss genau dem Impressum entsprechen; der Betreff hat kein „Re:“. Nur Text, kein Bild, kein Tracking-Link; Kopfzeilen wie in D4. Antworte mit `Mensch`: im Pult erscheint „Gespräch mit einem Menschen gewünscht“ (Etikett „Mensch gewünscht“). Klicke dann den Abmelden-Link (nur mit dieser Plus-Adresse: sie steht danach für immer auf der Sperrliste; zum Aufräumen die Id berechnen und das Dokument in `workersSuppression` löschen wie in P4 G13). *Erwartet:* alles wie beschrieben; der Link zeigt erst eine Seite mit dem Knopf „Abmelden“, dann „Sie wurden abgemeldet.“ *Wenn nicht:* jede Abweichung im Text (Reste wie Telefon oder Adresse in der Anrede, ein zweiter Link, ein falscher Angebotssatz) oder ein Eintrag „Mail nicht gesendet: …“ im Reiter „Freigabe“ mit Wortlaut an Claude, bevor du Räume verbindest. + +**E6. Kürzen-Probe (an `kalt2`).** Echte Kürzungen kämen sonst erst nach 29 bis 89 Tagen. Wenn `kalt2` die Mail bekommen hat (Status `kontaktiert`, **nicht** antworten), setze in der Appwrite-Konsole (`workersProspects`, Dokument `kalt2…`) `purgeAfter` auf gestern. Am nächsten Morgen nach 03:00 (der Lauf schreibt kein Ereignis): im Dokument `name` = `[gekürzt]`, `email` leer, `purgedAt` gefüllt, `purgeAfter` leer; im Faden (`threadId` aus dem Dokument, Sammlung `workersThreads`) `toEmail` leer und `status` = `abgeschlossen`; die Nachrichten des Fadens (`workersMessages`, Feld `threadId`) haben `body` = `[gekürzt]`; die Anzahl der Dokumente in `workersKeys` ist gleich geblieben. *Erwartet:* alles wie beschrieben und `docker logs --since 24h webklar-workers 2>&1 | command grep -c 'Kürzung:'` gibt `0`. *Wenn nicht:* eine Zeile `Kürzung: Interessent … nicht gekürzt` oder `Fehler bei kuerzen` an Claude; **Räume nicht verbinden**, denn die Datenschutz-Zusage (90, 30 Tage, sechs Monate) hängt an diesem Lauf. Danach darfst du die Probe-Dokumente in der Konsole löschen. + +**E7. Mails pro Tag auf 1 stellen** (empfohlen für die ersten Tage: neue Domain, kleine Mengen). WOMS → Hustler → Hochhaus → „Einstellungen“ (Admin) → Gruppe „Mails“ → „Unverlangte Mails pro Tag“ = `1`, speichern. Steigerung: Sendetage 1 bis 4 eine, ab dem 5. zwei, ab dem 8. drei (ab dem 8. Tag belegen fällige Erinnerungen Plätze). *Erwartet:* der Dialog schließt ohne Fehler. *Wenn nicht:* eine rote Zeile im Dialog nennt die Grenze (0 bis 3). + +**E8. Räume verbinden** (am Werktag **vor 22:00**: danach hat Otto die Nacht schon geplant und die Suche beginnt erst am nächsten Abend; der Schalter muss an sein, sonst verweigert das Skript mit Exit 1). Erst ein Raum: +``` +docker exec webklar-workers node scripts/p6-freischalten.mjs raeume --set handwerk +``` +*Erwartet:* `Verbundene Räume: handwerk` (steht dort schon etwas von der Anfragen-Etage, etwa `netz, foren`, bleibt es erhalten). `aerzte`, `selbst` und `laden` kommen erst nach einer Woche ohne Bounce und mit lesbaren Mails dazu: derselbe Befehl mit dem Raumnamen, mehrere mit Komma (`raeume --set aerzte,selbst`). *Wenn nicht:* `Der Datenschutz-Schalter ist aus: erst „datenschutz --check“, dann „datenschutz --set“, danach die Räume verbinden.` = E2 und E3; `Places-Schlüssel fehlt (WORKERS_PLACES_API_KEY): Räume bleiben unverbunden.` = C1 und C5; `Unbekannter Raum: … (erlaubt: aerzte, handwerk, selbst, laden)` = Tippfehler. + +#### F. Erste Nacht und erste Woche + +**F1. Was geschieht, kein Befehl.** 22:00 Otto: „Otto schickt Hanna los: Handwerk in Rheinland-Pfalz (10 Kandidaten).“ (bei mehreren Räumen der, der am längsten nicht gesucht hat). Der Scout holt den Auftrag innerhalb von 2 Minuten (Lauf höchstens 20 Minuten): je neuem Betrieb „… neuer Kandidat in ()“, schon bekannte „Schon bekannt: Kandidat in “ (Arne), dann „… 10 Kandidaten an Bea übergeben“ und bei Bea „10 Kandidaten bewertet, bester Wert N“; wer nicht in Frage kommt, steht als „Bot-Lead (, ): “ in der Liste. 03:00 Arne kürzt (still), 03:40 räumt der Dienst das Postfach auf (ein Ereignis nur, wenn er etwas löscht). 05:30 Bea: „Platz 1 von 5: Punktzahl 84“ (an Max), alle anderen als Bot-Lead „Platz 2 von 5; freie Mail-Plätze: 1“ (die Zahl nennt die freien Plätze vor der Wahl, mit „Unverlangte Mails pro Tag“ = 1 also 1; „…: 0“ steht nur, wenn für den Tag schon alles belegt ist) oder „Punktzahl unter 55“. Ab 09:00, nur Mo bis Fr, schreibt Max („Erstmail entworfen ()“) und sendet („Erstmail gesendet“); Feiertage sind nicht ausgenommen, am Wochenende Gewählte gehen am Montag hinaus. Sind alle Mail-Plätze belegt, sucht der Scout nicht („… Alle Mail-Plätze sind vergeben, kein Suchlauf.“): kein Fehler. *Erwartet:* die Spur der ersten Nacht in dieser Reihenfolge. *Wenn nicht:* fehlt die Zeile von 22:00, steht der Raum nicht in „Verbundene Räume“ (E8) oder der Dienst ist angehalten; fehlt die von 05:30 an einem Tag, an dem du nach 05:30 eingeschaltet hast, ist das richtig (E3). + +**F2. Morgenkontrolle (2 Minuten).** Erst das Log der Nacht: +``` +docker logs --since 24h webklar-workers 2>&1 | command grep -E 'Fehler bei|fehlgeschlagen|Scouts:|Scout [a-z]+:|Postfach aufräumen: (fehl|kein|UID)|Kürzung:|Top-3-Auswahl|Sende-Tor|Tick abgebrochen|Unbehandelte' | command grep -v 'OSM-Abfrage\|Lesefehler' +``` +*Erwartet:* keine Zeile (Zeilen `Scout : Lesefehler {…}` mit Netzkennungen wie `ENOTFOUND` sind normal, tote Websites, und der Befehl blendet sie aus). Programmfehler unter den Lesefehlern zeigt dieser Befehl: +``` +docker logs --since 24h webklar-workers 2>&1 | command grep 'Lesefehler' | command grep -E 'TypeError|ReferenceError|RangeError|SyntaxError' +``` +*Erwartet:* keine Zeile; sonst Claude. Dann die Störungen von Overpass (OSM), das in der ersten Nacht zum ersten Mal echt gefragt wird (der Dienst loggt sie als „OSM gestört“, nicht als Fehler des Laufs): +``` +docker logs --since 24h webklar-workers 2>&1 | command grep -cE 'OSM gestört|OSM-Abfrage fehlgeschlagen' +``` +*Erwartet:* höchstens `1` je Suchlauf der Nacht (`OSM gestört, keine weitere Anfrage in diesem Lauf (…)` steht höchstens einmal je Lauf; die öffentliche Overpass-Instanz antwortet gelegentlich mit 429 oder 504). Dazu: Google Cloud zeigt mindestens 1 und höchstens 3 Places-Anfragen der Nacht, die Anthropic-Console (Workspace „Hustler“) unter 1 $ für die Nacht, und WOMS → Hustler → „Bot-Leads“ die Gründe: „Bewertung ausgefallen“ und „Lesen fehlgeschlagen“ sollten gegen null gehen, „keine veröffentlichte Mail“ darf überwiegen; die Zahl „Bot-Leads gesamt“ im Hochhaus gleicht der Summe im Reiter, und im Raum „Ärzte & Praxen“ steht bei jeder Zeile „bewusst nicht gespeichert“ statt einer Nummer. Optional: Ein Klick im Hochhaus bei Arne auf eine Zeile „Bot-Lead (…)“ öffnet `/hustler?tab=botleads&bot=` mit genau diesem hervorgehobenen Eintrag und dem Knopf „Alle anzeigen“; „Alle anzeigen“ zeigt die ganze Liste. *Wenn nicht:* jede Nacht eine OSM-Zeile (dann ist ein Abfragemuster kaputt) oder viele „Bewertung ausgefallen“ (Beas Antwort wird abgeschnitten) oder über 80 % „keine veröffentlichte Mail“ trotz guter Probe in D = Claude. + +Willst du die Suche schon tagsüber sehen: `scripts/jetzt.mjs` löst nur `bericht`, `nachtplan` und `nacht2` aus; `top3` (05:30) und `kuerzen` (03:00) kannst du nicht von Hand starten. Das echte Planen der Suchläufe (etwa 0,1 bis 0,3 $; die gefundenen Betriebe kommen am nächsten Morgen in die Auswahl): +``` +docker exec webklar-workers node scripts/jetzt.mjs nachtplan +``` +*Erwartet:* eine JSON-Zeile `{"planned":[{"room":"handwerk","workerId":"hanna","region":"Rheinland-Pfalz","jobId":"…"}],"skipped":null}`; nach 2 Minuten holt der Scout den Auftrag. Der normale Lauf um 22:00 plant trotzdem noch einmal (jetzt.mjs setzt keinen Tagesmarker): in dieser Nacht suchen die Scouts zweimal, bis zu 0,2 $ mehr und bis zu 20 statt 10 Kandidaten. Sind die Räume der Anfragen-Etage verbunden (E8), stehen in `planned` auch `rita` und `finn` (Suche im Netz, wenige Cent). *Wenn nicht:* `"skipped":"keine_raeume"` = E8 fehlt, `"skipped":"pause"` = der Dienst ist angehalten. + +**F3. Die ersten Mails (ab 09:00).** Im Postfach der Absenderadresse gibt es **keinen** Ordner „Gesendet“ (Resend legt keine Kopie ab). Prüfe stattdessen: Appwrite-Konsole → `woms-database` → `workersDaily`, Dokument mit der Id des Tages (`JJJJ-MM-TT`): `mailsCold` ist höchstens so groß wie „Unverlangte Mails pro Tag“ (kein Wochenende: dort 0); im Hochhaus bei Max „Erstmail gesendet“; in Resend → Emails jede der ersten 10 Mails öffnen (Anrede ohne Reste wie Telefon oder Anschrift, Betreff, Angebotssatz wörtlich, genau ein Link, Fuß mit Quelle, Impressum, Datenschutz, Abmelden). *Erwartet:* alles wie in E5. *Wenn nicht:* ein Eintrag „Mail nicht gesendet: text_zu_lang“, „text_zu_kurz“ oder „link_anzahl“ im Reiter „Freigabe“ heißt: der Angebotstext in den Einstellungen passt nicht (die ganze Mail muss 60 bis 180 Wörter haben, der Angebotstext höchstens 400 Zeichen): Text kürzen, das Tor nie lockern. + +**F4. Täglich in den ersten vier Wochen, danach wöchentlich: Resend.** Bounces gehen an Resend, die Notbremse des Dienstes sieht sie kaum (Resend sperrt harte Bounces selbst). Resend → Emails, Filter „Bounced“ und „Complained“ der letzten 24 Stunden. Je Adresse in der Appwrite-Konsole `workersProspects` den Status auf `verloren` setzen (das stoppt die Erinnerung); bei einer Beschwerde zusätzlich „Unverlangte Mails pro Tag“ = 0. *Erwartet:* keine Treffer. *Wenn nicht:* ab 2 harten Bounces oder einer Beschwerde in einer Woche Schalter aus (H1) und Claude. + +**F5. Tag 8 und Tag 30.** Ab dem 8. Tag kommen die ersten Erinnerungen: Betreff „Kurze Erinnerung: …“ ohne „Re:“, nach Anrede und KI-Satz beginnt der Text mit „Ich melde mich noch einmal kurz zu unserer Nachricht vom TT.MM.JJJJ“, Erst- und Erinnerungsmails zusammen höchstens 3 pro Tag und höchstens zwei Mails je Betrieb. Ab jetzt leerst du das Postfach nicht mehr monatlich von Hand: der Lauf um 03:40 löscht jede Mail, die der Dienst schon aufgenommen hat (alles bis zu seinem Lesestand, egal von wem und ob du sie gelesen hast) und die älter als 29 Tage ist, höchstens 500 am Tag. Liegt noch ein Rückstau aus der P4-Zeit im Postfach, meldet Arne das schon in der ersten Nacht („Antwort-Postfach aufgeräumt: N Mails gelöscht“), sonst erst, wenn die ersten Antworten 29 Tage alt sind. Steht stattdessen „Fehler bei postfachAufraeumen: …“ im Hochhaus, prüfe Login und Löschrechte des Kontos beim Anbieter. Steht im Hochhaus stattdessen „Antwort-Postfach: N Mails zum Löschen markiert (UIDPLUS fehlt)“, kann der Server einzelne Mails nicht entfernen (der Dienst markiert sie nur mit \Deleted, ein einfaches EXPUNGE nähme auch Mails mit, die ein Mensch markiert hat): dann ist nichts gelöscht, die Zusage „30 Tage“ hält nicht, Claude Bescheid; bis zur Klärung leerst du das Postfach von Hand. + +**F6. Kosten.** Pro Nacht: Places höchstens 3 Anfragen zu je 0,035 $ (0,105 $; prüfe auf der ersten Google-Rechnungszeile, dass Text Search wirklich 0,035 $ je Anfrage kostet), Bea ein Sonnet-Aufruf (grob 0,03 bis 0,05 $), Max 1 bis 3 Entwürfe (grob 0,01 bis 0,02 $ je Mail), Antworten Cent-Beträge: erwartet unter 0,3 $ pro Tag und unter 10 $ im Monat; die Grenzen im Dienst sind 6 $ pro Tag und 150 $ im Monat. Resend Free (laut Resend 100 Mails am Tag, 3000 im Monat) reicht, Cloudflare Email Routing kostet nichts, die Domain wenige Euro im Jahr. *Erwartet:* „API heute“ im Hochhaus und die Anthropic-Console bleiben klar darunter. *Wenn nicht:* deutlich mehr = „Alles anhalten“ (H1) und Claude. + +#### G. Handfälle + +**G1. Bot-Lead „Name fehlt“, „Impressum passt nicht zum Betrieb“, „schon bekannt (E-Mail oder Telefon)“ oder „(Name und PLZ)“.** Kein Befehl. „Name fehlt“: die Seite nennt keinen echten Betriebsnamen (nur „Startseite“, „Willkommen …“ oder „Inh. …“). „Impressum passt nicht zum Betrieb“: Google verlinkt eine fremde Seite (Verein, Städteportal, Franchisegeber, Agentur). „Schon bekannt“: Register oder Sperrliste kennen den Betrieb. In allen Fällen geht keine Mail hinaus, und die Adresse ist beim Ablegen gelöscht. Willst du den Betrieb trotzdem erreichen, öffne „Quelle öffnen“ in der Bot-Lead-Zeile und schreib selbst, nicht über den Hustler. *Erwartet:* ein paar solche Zeilen pro Nacht. *Wenn nicht:* die Mehrzahl der Funde über mehrere Nächte = Claude (die Regeln kosten bewusst Ausbeute, so viele deuten auf einen Lesefehler). + +**G2. Ein Betrieb, den du schon kennst** (Kunde, früherer Kontakt, ein Anruf), der noch nicht im Register steht: vor der Mail auf die Sperrliste. Eingetragen wird die Adresse, ihre Domain (außer bei Freemail) oder eine Telefonnummer: +``` +docker exec -i webklar-workers node --input-type=module <<'JS' +import { loadConfig } from './src/config.js' +import { createDb } from './src/db.js' +import { suppress } from './src/register/keys.js' +const deps = { db: createDb(loadConfig().appwrite), clock: { now: () => Date.now() }, log: console.log } +console.log(await suppress(deps, { email: 'info@beispiel.de' }, 'manuell', 'mensch:kenso')) +JS +``` +*Erwartet:* `{ created: [ 'mail', 'dom' ], ignored: false, unparsed: [] }` (bei einer Freemail-Adresse nur `mail`); ab dann verweigert das Sende-Tor jede Mail an diese Adresse (`gesperrt`). Statt `email` geht auch `domain: 'beispiel.de'` oder `phone: '+49 …'`. *Wenn nicht:* `ignored: true` = die Angabe war nicht lesbar, nichts ist gesperrt: Tippfehler prüfen. + +**G3. Widerspruch, „abmelden“, Löschverlangen, Beschwerde, Abmahnung.** Bei „abmelden“ (Antwort oder Link), Abmahnung und Beschwerde-Wörtern sperrt Nora den Absender selbst und sofort, auch in der Pause (Ereignis „Abmeldung: Interessent gesperrt“; bei Beschwerden dazu das Pult „Beschwerde oder rechtliche Drohung“ und die Mail „[Hustler] Beschwerde oder rechtliche Drohung“). Einen Widerspruch oder ein Löschverlangen in eigenen Worten beurteilt Nora mit dem Modell: sie sperrt oder legt die Antwort als „Unsichere Antwort“ ins Pult. Dazu musst du von Hand: (a) die Antwort-Mail und jede Mail, die sie zitiert, **am selben Tag** im Hetzner-Postfach löschen (der Text verspricht „sofort“, die automatische Frist von 30 Tagen genügt hier nicht); (b) bei einer Beschwerde oder Abmahnung nichts antworten ohne Rechtsrat und solange die Kanzlei prüft den Schalter aus (H1, Stufe 2); (c) verlangt die Person Auskunft oder Löschung vor der Frist, Claude Bescheid: er klärt, was in der Datenbank (sonst kürzt Arne nach 29 Tagen) und im Resend-Protokoll (nach Resends Frist, sonst beim Resend-Support) zu der Adresse steht. Eine Sperre ist endgültig: nie wieder anschreiben; nur deine eigenen Testadressen löschst du aus `workersSuppression` (P4 G13). *Erwartet:* die Adresse ist gesperrt, das Postfach ist leer von ihr. *Wenn nicht:* eine Antwort, die Nora nicht erkennt, liegt als „Unsichere Antwort“ im Pult: lies sie selbst und sperre sie mit G2. + +**G4. Resend sperrt das Konto oder der Login geht nicht mehr.** Du erkennst es an einer Mail von Resend, an der Zeile „SMTP-Login abgelehnt (…), bis zum nächsten Berliner Tag kein weiterer Versuch“ im Hochhaus und an der Mail „[Hustler] Versand an Interessenten gestoppt (SMTP-Login)“. Dann: **nicht** in einer Schleife neu versuchen; im Resend-Dashboard den Grund lesen (Bounce-Quote, Beschwerde, Bedingungen); solange er unklar ist, Schalter aus (H1, Stufe 2), die Aufträge bleiben offen. Stimmen nur die Zugangsdaten nicht (Schlüssel erneuert), setzt du `WORKERS_SMTP_PASS` in der `.env` neu und startest neu. Ein anderer Anbieter oder eine neue Domain heißt neue DNS-Einträge und neue `WORKERS_SMTP_*`: Claude; nie von `webklar.com` senden. *Erwartet:* kein Versand, bis der Grund geklärt ist. *Wenn nicht:* Grund unklar = Claude, nichts raten. + +#### H. Rückweg + +**H1. Sofort stoppen** (wirkt ohne Neustart, verliert nichts). In vier Stufen: +- Stufe 1, alles: WOMS → Hustler → Hochhaus → „Alles anhalten“ → „Anhalten“ (Scouts, Top 3 und Versand stehen; ein laufender Suchlauf von bis zu 20 Minuten endet noch, sendet aber nichts; Kürzen und Postfach-Aufräumen laufen weiter). +- Stufe 2, keine Kaltmail und keine Erstantwort von Clara: +``` +docker exec webklar-workers node scripts/p6-freischalten.mjs datenschutz --off +``` +- Stufe 3, keine neue Suche (trennt nur die vier Scout-Räume, die der Anfragen-Etage bleiben; schon eingereihte Erstmail-Aufträge gehen bei eingeschaltetem Schalter noch hinaus, nimm dafür Stufe 2): +``` +docker exec webklar-workers node scripts/p6-freischalten.mjs raeume --clear +``` +- Weich: „Unverlangte Mails pro Tag“ = `0` (die Scouts melden „Alle Mail-Plätze sind vergeben, kein Suchlauf.“). + +*Erwartet:* `Schalter privacyInteressentenLive: aus` bzw. `Verbundene Räume: …` ohne die Scout-Räume. *Wenn nicht:* Fehlermeldung an Claude. + +**H2. P6-Code zurücknehmen** (nur nach H1, dort mindestens Stufe 3, solange der neue Dienst noch läuft: der alte Dienst kennt die Scouts nicht, plant aber mit den verbundenen Räumen weiter jede Nacht Suchaufträge für sie, die niemand abholt; altes Image und alter Stand gehören zusammen, der Dienst liest die Rollen-Texte aus dem Checkout; `.env` und Compose müssen nicht zurück, der alte Dienst ignoriert die neuen Einträge; willst du es doch: `cp -p .env.bak--workers-p6 .env` und `cp -p docker-compose.yml.bak--workers-p6 docker-compose.yml` im Ordner `/home/webklar/apps`, die Sicherungen aus B1). +``` +docker tag webklar-workers:vor-p6 webklar-workers:latest && git -C /home/webklar/apps/workers checkout --detach "$(cat /home/webklar/apps/workers-alt-p6.txt)" && cd /home/webklar/apps && docker compose up -d --no-deps workers +``` +*Erwartet:* `curl -s https://ticket.webklar.com/api/workers/health` antwortet wie in A1; `curl -s -o /dev/null -w '%{http_code}\n' https://ticket.webklar.com/api/workers/botleads` gibt `404`. Vor dem nächsten Update `git -C /home/webklar/apps/workers checkout main`, sonst scheitert `git pull --ff-only`. *Wenn nicht:* Bau- oder Startfehler an Claude. + +**H3. WOMS, Portal und Website zurücknehmen** (nur falls nötig). Claude führt `git revert --no-edit` der betroffenen Commits aus (nie die Geschichte umschreiben), du pushst wie in B6, Teil A Schritt 5 und 8. Soll nur der Betrieb stoppen, genügt H1; die neue Fassung in WOMS → Rechtliches bleibt stehen (P3 und P4 brauchen sie auch). *Erwartet:* die Deploys laufen durch wie auf dem Hinweg. *Wenn nicht:* Claude. + +Was beim Rückweg bleibt: Alle Daten (Interessenten, Fäden, Nachrichten, Aufträge, Sperrliste, Register, Suchprotokoll) und die Schema-Felder. Läuft der alte Dienst, **laufen die Fristen des Datenschutztextes weiter, aber niemand kürzt**: Kürzen (90 Tage, 30 Tage, sechs Monate) und das Aufräumen des Postfachs (30 Tage) gibt es nur im P6-Dienst. Bleibt P6 länger als ein paar Tage aus, leere das Postfach monatlich von Hand und sag Claude Bescheid. + +### Laufende Pflege + +- Den „Angebotstext“ änderst du nur, wenn es keine offenen Fäden oder Angebote gibt (jedes Angebot speichert den Satz, der in der Mail stand). +- Nach jeder Änderung am Datenschutztext (Abschnitt 11): `datenschutz --check` (E2) erneut, PDF neu drucken, neue Version in Rechtliches hochladen (Teil A Schritt 4 bis 10). +- „Nachfassen nach (Tage)“, „Aufbewahrung (Tage)“ und „Unverlangte Mails pro Tag“ nur innerhalb der Zusagen im Text (sieben Tage, höchstens 90 Tage, höchstens 3 pro Tag). +- Änderungen an den Rollen-Prompts (`prompts/bea.md`, `prompts/max.md`) wirken nach `git -C /home/webklar/apps/workers pull --ff-only` ohne Neubau; Änderungen an Aufgabentexten im Code brauchen ein neues Image (B3, B4, C5). + +**Fertig, wenn:** Teil A (Schritt 6, 8, 10), A1 bis A6, C6, C7, D2 bis D4, E2, E3, E5, E6 und F2 ihre „Erwartet“-Zeilen erfüllen. Der Dauerbetrieb mit mehreren Räumen und drei Mails pro Tag ist eine eigene Entscheidung nach der ersten Woche (F4, F5).